What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Unit Testing in Angular
by Joe Eames
Automated tests are an important ingredient in a successful project. This course will teach you everything you need to know to unit test your Angular projects, including testing services, component templates, and dealing with asynchronous code.
Resume CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Course Overview
Course Overview
Hi there. I'm Joe Eames, and welcome to my course, Unit Testing in Angular. I'm a Google developer expert in Angular and have been working with AngularJS since 2010 and Angular since it was in alpha. Angular is an amazing, modern, highly performant framework, and learning it one of the best things that you can do for your career. But it's no good to write code if your code doesn't work. That's where unit testing comes in. In this course we're going to learn all the tools and techniques to effectively unit test Angular projects. Some of the things we will cover are isolated tests for quick and easy testing of components, services, and pipes, integration tests for testing components with their templates, and dealing with asynchronous code in your tests. We'll also see how to write good, maintainable tests. By the time we're through, we'll understand the tools that Angular provides for unit testing your code, how to test your projects effectively, and how to make those tests maintainable. Before starting this course, you should be familiar with JavaScript and Angular. I hope you'll join me on this journey to learn how to unit test Angular with the Unit Testing in Angular course at Pluralsight.
Course Introduction
Course Introduction
Hello, my name is Joe Eames, and welcome to Pluralsight's course, Unit Testing in Angular. I'm excited to present this course to you. I have used AngularJS and Angular for over 7 years now and have built some amazing things with it, and I've always found it to be a great solution for whatever application I need to build. I'm also a big fan of automated testing, so this is a course that is near and dear to my heart. In this course we're going to start off with this introductory module, in which we'll talk about the goals of this course, the demo application that we're going to be using throughout this course, we'll talk about testing, and unit testing in specific, and then we'll talk about the tools that we're going be using while we're unit testing Angular. Then in future modules we're going to be talking about isolated unit tests, integration tests, which in Angular are a special kind of unit test, then we'll talk about testing DOM interaction and routing components, and then we'll finish up with some advanced topics. In this course we have one primary goal, which is the title of the course, we want to learn to write unit tests in Angular. But in the course of learning to do anything, we should always ask ourselves not just how, but why and when. Why should we write certain kinds of tests? And when should we write them? And when should we not write them? In other words, we want to write good unit tests. We want to accomplish the goal of automated testing, which is to increase the quality of our software. Tests are a tool that can help us with that, and we will keep a constant eye on that goal, the goal of writing good unit tests. Finally, Angular has two types of unit tests, isolated and integration tests. We will learn more about these later, but this will be our other secondary goal, which is to understand the difference between these two types of tests, to know how to write each kind of test, and when to write each kind of test. That leads us to talk about what this course is not about. First of all, this course is not about end-to-end testing. We're not going to look at any end-to-end testing tools, we're not going to write any end-to-end tests. One of the reasons for this is that most end-to-end testing tools really have nothing to do with the framework that you're using. So other courses you may watch about end-to-end testing are going to be just as applicable to Angular as they are to any other web framework. We're not going to spend much time looking at the tools themselves, understanding how they work, we're not going to dig down into their details. No matter what tool you use to test Angular this course will be applicable to you. This course is not about testing first versus testing after, or TDD, which is test-driven development. Even though I'm a fan of TDD, this course is not going to be discussing TDD specifically. Whether you write your test before you write your code or after you write you code, really won't matter, this course is applicable to you in either case. Finally, it's important to understand what you need to know in order to get the most out of this course. You should have a basic understanding of Angular, and I do mean the current version of Angular, not AngularJS version 1.x. It doesn't matter what version of Angular you're using, this course will be applicable to you. Also, you should have a basic understanding of the web. You should know about HTTP and HTML, and how the web works in general. You should understand browsers and their role in web applications.
The Demo Application
Let's take our first look at the demo application that we're going to be testing in this course. This is the course repository and that has two important things on it. One is it has our starting code. So this where you can go and get the starting point for the application that we're going to be testing. The next thing it has that's important is it has a read-me document that can tell you whether or not the course is completely up to date. Testing is one of those topics that doesn't matter if you're using the latest version so long as the principles that you learn and the techniques that you learn are still applicable to the latest version of Angular. So I expect this course to stay up to date for a relatively long time, but if there's ever anything that is out of date, the read-me will tell you what is out of date and talk about workarounds for it. It would be a good idea to take a quick look at this repository right now, you can check out the current status of the course, but don't worry about cloning it and installing it yet, we'll actually do that together later on. This demo is actually based on the Tour of Heroes. I took a snapshot of the Tour of Heroes at the time that I was preparing this course. I did modify it a little bit, I had to add a few things so that we have exactly the kinds of stuff that we want to test, so it's a little bit different than the basic Tour of Heroes, but the modifications are fairly small. And finally, a note, don't worry about the package versions. Again, the important part is that the techniques and tactics that we learn are going to be useful with the current version of Angular, whether or not we're actually using a slightly older version of Angular is unimportant, just so long as what we learn is applicable to what is latest. Now let's jump over and take a look at our running application. This is application we're going to be testing, it's an application that deals with superheroes, you've got a dashboard here that shows your top four heroes, you can see the list of heroes on this page, you can click in and see the details on a specific hero. It's a very basic application, and in this course we really won't be worried about the application running; we're not modifying the application, adding new features to it, so running the application is not going to be important. What is going to be important is the tests that we'll write. So, for the most part, we're not going to be looking at the application running in a browser. Now let's head over to our code and look at the code for the Tour of Heroes application. This is a basic CLI application. Everything that we're going to be dealing with for the most part is here inside of the app directory. We have your typical stuff, we have an app.module that we'll look at a couple of times, and then we've got a whole bunch of components that are inside of each of these folders. We've got our dashboard component, it's HTML and CSS files, same thing for the hero component, hero-detail, etc., on down to this messages component. Finally we have a strength pipe, and then further on down here we have our services, the hero.service, the in-memory-data.service, and the message.service. And that's our application. Again, this is fairly straightforward application, we're just going to be adding tests for each of these pieces.
Testing Overview
In this section we're going to talk about automated testing in general. There are three types of automated tests. Unit testing, which is what we're going to be dealing with in this course, end-to-end testing, and integration, or functional, testing. Unit testing and end-to-end testing are fairly well defined, integration or functional testing tends to be a little bit more of a vague concept that can mean one thing to one person and something different to somebody else. Let's take a look at each of these types of tests. First we have end-to-end testing. End-to-end testing is the kind of testing that is done against a live, running application. This means the full application with a live database, live server, live front-end. Then we write tests that exercise that live application. This is generally done through automating the browser. Tests are written to manipulate the browser in an automated way, to do things like click buttons, type values into forms, navigate around, and similar tasks. The benefit of end-to-end testing is you can validate that your application works as a whole. There are plenty of drawbacks to end-to-end testing, which have to do with speed and difficulty of writing tests, and generally end-to-end testing tends to be less reliable than other types of automated tests. Next we go to the other end of the spectrum, which is unit testing. Unit testing is done against a single unit of code. Notice how I put the word unit in quotes. That's because the word unit can mean different things to different people. Generally, the accepted unit of code is a single class. Although, in some cases, you may define a unit as more than a single class. Next we have everything in between, integration and functional testing. Integration and functional testing is defined as more than a unit, but less than the complete application. So it's at least two units working together; oftentimes, these types of tests are used to check that a certain part of the application works with another part of the application. What is an integration or functional test? It's somewhat of a vague concept that can mean a lot of things to a lot of different people. Let's look at some diagrams of these concepts. Here we're looking at end-to-end tests. We've got our live application, which includes our front-end, our web server, and our database, all working together. With end-to-end tests we create tests that automate that front-end. We write tests that manipulate the browser and exercise our application to prove that it's working. With integrational and functional testing we've zoomed in. In this case we've gone into our front-end and now we're using two different pieces of our front-end that are related and work together, but not necessarily the same unit. In our case it's our user component and our user service. In an integration or functional test we might test that those two components work together correctly. To do that we write integration tests that use the user component, which also uses the live user service, and then we might check and see what kind of HTTP calls the user service is making or what values it returns back to the user component. Finally, we have unit testing. In this case we just take a single unit of code, again our user component, and we write tests against that one unit of code. Unit testing is usually the most talked about kind of testing, and generally, in development, we write more unit tests than we write of any other kind of test. Again, there can be a little bit or argument about what is a unit. For example, if we had a user helper class we might consider that and the user component to be a single unit of code and we might test those together. Although this course is about unit testing, what we are going to cover will actually bleed a little bit into the integration or functional testing bin than we talked about previously. The reason for this is the nature of how Angular components work. Components in Angular have templates. So Angular has tooling that allows for a special kind of test, which they call an integration test. It uses the template and the component together to make sure that those two pieces are working correctly together. Now again, is this truly an integration test, or is it just another kind of unit test where we have two different pieces that are really just the same unit of code? That's up for you to decide, but the name of this type of testing is officially called integration tests. That is used by the Angular team itself. So we will be calling these integration tests, even though there is some debate as to whether or not this is traditional unit test.
Mocking
One of the very important concepts in the unit testing is mocking. Mocking allows us to make sure that we are only testing a single unit of code at a time. For the most part, a class doesn't operate in isolation, most classes or components have dependencies. In this example a user component like this probably doesn't work by itself, it likely would use something like a user service, which is injected into the component. But when we write our unit tests we don't want to use the real user service, there are a lot of reasons for this. First, we're just trying to test the user component. It's a unit test. We don't want to test the user service as well, we'll write separate unit tests for that. Also, this user service might make HTTP calls, which we don't want in a unit test. So we need to draw a boundary around the unit that we're testing. Again, in this case, that's the user component. So we will do that with mocks. Instead of using the real user service we're going to provide a mock user service. A mock is a class that looks like the real class, but we can control what it does, what its methods return, and we can ask it questions about what methods were called during a test. Although most people just use the generic term mock, there are actually several types of objects that do various things related to mocking. The simplest kind is a dummy. Dummies are just objects that fill a place. They generally don't do much interesting, they're just used in place of a real object, like if a method call requires a parameter that's an object, but it doesn't care what that object is then a dummy is the perfect thing for that. A stub is an object that has controllable behavior. If we call a certain method on a stub we can decide in our test what value that method call will return. A spy is an object that keeps track of which of its methods were called, and how many times they were called, and what parameters were used for each call. Most of the time, these are the types of objects that we use when we need a mock, and many times the boundaries between these three can be a little blurred. We might use objects that have the behavior of both stubs and spies for example. But there is one other type of object that we can use, a true mock. These are more complex objects that verify that they were used in exactly a specific way. For example, they can check that only a specific method was called, and that is was called only once, and it had some very specific parameters, and they're able to do this to themselves. They a bit more difficult to work with and are usually overkill for what most unit tests need, but we will see an example of a kind of true mock when we get into testing components that use HTTP.
Unit Tests in Angular
In this section we're going to talk about the different types of unit tests that are available in Angular. Angular actually has several different kinds of unit tests, and it's important to know what each of them are and how they differ from each other. The basic unit test is an isolated test. This is what we think of when we think of a unit test. In an isolated test we simply exercise a single unit of code, either the class of a component, or the class of a service, or a pipe, we construct that class by hand, and we give it its construction parameters ourselves. An integration test is a bit more complex. In an Angular integration test we actually create a module in which we put just the code that we're going to test, generally just one component, but we actually test that in the context of an Angular module. This is used so that we can test the component with its template. There are two types of integration tests supported in Angular, shallow integration tests where we only test a single component, and deep integration tests, the difference being that many components actually have child components. Sometimes, we want to test both the parent component and the child component and how they work together. That is a deep integration test. We will see example of all of these kinds of tests in this course.
Tools of Unit Testing with Angular
Let's go over the various tools that are used when unit testing Angular. The tools that we will be using in this course are the default tools that are used when testing an Angular application. The CLI sets up testing for you and it uses two different tools, Karma, which is the test runner, this is what actually executes our tests in a browser, and Jasmine. Jasmine is the tool we use to create mocks, and it's the tool that we use to make sure that the tests work the way that we want them to using expectations. There are quite a few other unit testing tools that are available for unit testing with Angular. There's a very popular library called Jest that's been put out by facebook and it's really popular with other frameworks but can be used with Angular. There's Mocha and Chai, those are replacements for Jasmine. They are somewhat popular and they're easy to drop in and replace Jasmine with. There's Sinon, which is a specialized mocking library. If we find that the mocking capabilities in Jasmine aren't good enough then we can use something like Sinon. There's TestDouble, which is a competitor to Sinon, that's gaining some popularity, but is still far less popular than Sinon. There's a tool called Wallaby, this is a paid tool that allows you to see the code coverage of your tests right in your IDE. It's very convenient and it's getting to be a popular tool. Cypress is traditionally considered to be an end-to-end testing tool, but they are developing capabilities to do more integration types of testing, so in the future we may see Cypress become more popular with Angular. Finally, there are tons of end-to-end testing tools, again, this course is not about end-to-end tests, so we won't be dealing with them, but it is important to know that if you are going to write end-to-end tests with Angular you have a lot of choices.
Installing and Running the Demo
Now let's install and run our demo. I'm here on a command line, I've already got Node and npm installed. I'm using the latest, or nearly the latest, of those two tools. So I'm going to clone that repo with git clone, then I'll paste in the URL, but I don't want the directory to be named this directory, I want it to have a different directory, so I'm going to name the directory ngUnitTestingDemo. And I'll hit Enter, and that clones the project into that directory, I'll go into that directory, and now I'm going to use npm to install all the modules with npm install. And once that install is done I can go ahead and run the application with npm start. With the application launched and running I'm going to head over a browser and go to URL localhost on port 4200, which validates that everything's working okay. Again, I don't need to have the application running, so let me go ahead and end the web server by hitting Ctrl+C, and finally, I'm going to open up the code in my favorite IDE. In my case I like Visual Studio Code so I'm going to open it up in Visual Studio Code, and I happen to have a little shortcut I can use from my command line, and that opens up the application inside of VS Code, and now we're ready to write our first unit test.
Writing Your First Unit Test
We are going to write our first unit test. So to do that I'm going to go inside the source directory, inside of the app directory, and right here I'm going to create a new file called first-test.spec.ts. The spec is important, that's what lets our unit testing tool Karma know that this is a test file. Spec is short for specification, it's a common word used when writing unit tests, so we need to make sure that all of our unit tests end with .spec.ts. That file created, we're going to start off with a describe function. This is a Jasmine function that lets us group tests together. So the describe function has two parameters. The first one is a string and the second one is s callback function that will contain our tests. So for the string, which is kind of a descriptor of this part of our tests, I'm going to put in the string my first test, and then a callback function, and inside of here is where we write our first test. I'm going to start off with a variable declaration. I'm going create a variable called sut, which is short for system under test, and then I want some common setup that's going to run before every test. This will reset the state so that I know that with every test we don't have any effects from a previous test that's holding over and perhaps polluting the state of future tests. And that is done in the beforeEach function, which just takes the callback, and before each test I want to initialize this sut variable to be an empty object. That way, no matter what happens in any test, no matter how we modify the sut object, it will always get reset back to its initial state for the next test. This is very important. you need to put code inside of your beforeEach that resets your state so that you make sure that you don't pollute future tests with changes that have been made in previous tests. Next we write an actual test, which we use with an it function. And again, just like the describe, it takes two parameters, the first is a string, and in our case, I'm going to put in the string should be true if true. It's very customary to start your it statements with the word should. The reason for this is that when we see the output of our tests we're going to see the string of the test that either passes or fails. And that's going to be appended with any describes that that it function sits inside of, and we can nest multiple levels of describes. So in our case, if this test were to fail, we would see the string my first test should be true if true. This string will show up next to our failing notification. It's a really good idea when writing your tests to make sure that these strings when appended together make sense. In a more realistic example we might have something like user service should getUser method should retrieve the correct user. These statements become somewhat of a specification for a class that indicates what behavior it should have in order to be doing its job correctly. In this case, this would likely be split up into two different describes and an it function, probably like this. We'd have an outer describe for our user service, then inside of here an inner describe for our getUser method, and then finally our it function. All these three appended together will create the statement that we see up at the top, user service getUser method should retrieve the correct user. We put that together and it's a nice statement about how this class should work. So let's return to our first test, and let's finish this up. We've got the it statement as a second parameter, which is a callback, and here's where we finally write our test. Our sut object has been initialized to an empty object. I want to give that a new property called a, and I'm going to initialize that to false. So this is the arrange part of our a-a-a test structure. Next we need to take some kind of an action, we need to act on our system under test. So I'm going to set that a property equal to true. And finally, we have our assert, where we're going to expect, and again, this is a Jasmine function, this expect statement, I'm going to expect that that a property is true. We use the toBe method in order to assert that that's true. Now Jasmine has a whole bunch of these methods, they're called matchers, such as toBe, to be truthy, to be greater than or equal to, a lot of different methods like that. I'll leave it up to you to look in the Jasmine documentation and check out all of the different matchers that are available. Again, learning Jasmine is not the point of this course, the point of this course is to learn to test Angular. Now this whole test, as it stands right now, is a little bit of a silly example since all we do is create a property on an object, initialize it to one value, set it to another value, and then assert that it is the second value. But this is the core essence of what a test is. We get our initial state, we change that state, and then we assert that the new state is what we expect it to be. This is the essence of writing a unit test, and following this structure will help our unit tests be as effective as they can be.
Running Your Unit Tests
We've written our first test, now let's actually run it and see it in action. So I'll save these changes, and then we're going to go to the command line here in just a second, but let's check out what command we're going to run. I'm going to open up the package.json file and we're going to look inside of there at the scripts. Now this is pretty much the package.json file that you get when you install a brand new CLI project. These scripts here are the default scripts that are created for a new CLI project. They might change over time as the CLI gets updated, but essentially these are our basic scripts. It's this script right here that we really care about, the test script. You can see that under the hood all it runs is ng, space, test. So if we go to the command line, we could just type in ng, space, test, so long as we had the CLI installed globally. But let's not do that, let's run it the normal way that we should with an npm script, which is npm test. I'm going to execute that command, and that is going to launch Karma and run all the tests that karma can find, which in our case is going to be just that one test that we wrote. So, Karma has executed, and we can see down here the message that has executed 1 of 1 test, and that it was successful. It also opened up a browser and ran our tests, and let's look at the output of that. This is what the browser looks like. It's showing that my first test should be true, it's green, which means that it passed. And we see the message up above, 1 spec, or 1 test, 0 failures. So we've executed our test and it's passing. One thing to note, it's nice to have the console open when running Karma, because that way we can see console commands that were output. So, we can open up our console and watch it here. And I just like to keep that open all the time. Now as we go back into project and write new tests they'll automatically be picked up and run inside of Karma. We're ready now to start testing for real, so let's head back over and we're going to delete the test that we created because we no longer need it, it's not a real test. In the next module we're going to be writing real tests against our code. So delete that, move it to the recycle bin, and we're now ready to go.
Writing Good Unit Tests
Before we finish up this section of our course, we're going to talk about what it means to write good unit tests. Now, obviously, this is the type of subject that can be debated for hours, and hours, and there are volumes of blogs and books written on the subject. We're not going to spend hours on this topic, but as we go through this course we are going to keep an eye on what it means to write a good unit test and try as much as possible to make our unit tests as valuable as they can be. There are a few principles of writing good units tests that are generally agreed upon by the programming community at large. First, it's important to know how to structure a test. Structuring tests follows what's called the AAA pattern. First, we arrange all necessary preconditions and inputs, then, we act on the object or class under test, and finally, we assert that the expected results have occurred. In a good test we actually see the code doing these things in this order. First, we set up whatever we need to set up, then we make a change, and then we check that the change happened in the way that we expected. Another way that we look at this is that we set an initial state, we change the state, and then we check to make sure that the new state is correct. As we write our tests in this course we will be following this structure. There's also a concept in testing of DAMP versus DRY. DRY, or don't repeat yourself, is a common concept used in programming. When we're following the DRY principle we remove duplication from our code, we don't want any duplication of code in our application. Good tests though, operate under a different principle called the DAMP principle. The reason we call it DAMP is because we still want to mostly follow the DRY principle, but we will repeat ourselves if necessary. Let's talk about why that is. A good test should tell a story. The story is we start at a given place, we make a change, and we check that we arrived where we got. That complete story should be within the it function. We shouldn't need to look around a whole lot in order to understand what's going on in the test, or in order to understand the story. So there are some techniques that we can use in order to tell our story effectively. First, we should move less-interesting setup into our beforeEach. If there's some setup or initial state that needs to be there, but isn't critical for the test that we're creating, then we can move that setup into our beforeEach function. Critical setup though should be within the it function. So if we have two different tests that use the same piece of setup, but that setup is important to the story of what the test is, then we will duplicate that setup within the it block, rather than extracting them both out into the beforeEach, which would remove duplication. This is why we call our tests DAMP and not DRY. Finally, we want to make sure that we include the arrange, the act, and the assert inside of the it function as they are possible to do. Sometime we test the initial state of our code, so sometimes the arrange might be missing, but in general, we try to include all three pieces inside of our it function and not move those out into a common beforeEach. Following these principles will help you write better tests, but of course, there is plenty of art to writing good tests and there is no replacement for experience.
Summary
In this section of our course we covered a lot of introductory material. We looked at our demo application, which was a variation on the Tour of Heroes tutorial. We talked a lot about testing, but we specifically are going to be talking about unit testing in this course. Angular has various kinds of unit tests that it supports. Isolated, which are the basic kinds of unit tests, integration unit tests, which have two flavors, both shallow and deep. In this course we're going to busing Karma and Jasmine, but again, it's not important which tool we're using, no matter what tool you're using this course is going to apply to you. Finally, we talked about writing good tests, which comes down to one core principle, which is telling the story of the test, a good test should tell a story.
Isolated Unit Tests
Introduction
Hello, I'm Joe Eames, and welcome to this module of Pluralsight's Unit Testing in Angular. In this module we're going to be talking about isolated unit tests and writing some isolated unit tests. We're going to start off with this introduction, then we're going to look at how to test a pipe, this will of course be an isolated unit test, then we'll write an isolate unit test for a service, then a component, and then we'll look at mocking, and finally we'll talk abut interaction testing, which is a particular kind of testing where we check that methods were called in the way that we anticipate they should be called. Before we move into actually writing our tests we need to talk a bit about what isolated testing actually is. So let's go over to our code and look at some of the pieces we can actually test. Let's assume we're going to test a component, let's just open up our dashboard component and look at it very quickly. A component seems like it might be a difficult thing to test because of all this extra stuff up here. But remember, if we get rid of this part here, all that we've got really is just a class, which is a plain old piece of JavaScript, nothing special, we don't have to worry about the parts that are Angular specific. If you've ever done any other kinds of testing of code, you'll likely feel comfortable testing a piece of code like this. It's just a class, we can create instances of it ourselves and we can test that its functionality works the way that we expect. And that's true whether we're talking about our dashboard component or our hero component. Again, we can in our minds remove this code here and these decorators right here as well. All the stuff that seems very special and specific to Angular and might be getting in the way of how we think we're going to be testing our code we can conceptually ignore all that code and just test the component as we see it here. Of course we're not going to actually delete that code, we're going to test these components with these decorators in place, but we're just going to ignore them. Let's look at a pipe. Here we've got our strength pipe. All a pipe does is transform a value, so we can ignore the decorator about it, we can ignore the name, we can ignore all of this and just test this as a class that has one function that takes in a value and returns a string. The same thing goes for services. Here in our hero service, even though we've got this Injectable decorator we can simply ignore that and test the HeroService class as just a piece of JavaScript code. That's what isolated testing is. We're going to isolate a class, we're going to test it as a regular old piece of JavaScript, and the tools and techniques that we use are the same things that we would use to test plain old JavaScript code, and it's also the same tools and techniques that are used in testing a lot of other JavaScript frameworks.
Testing a Pipe
We are going to start by testing this StrengthPipe. The reason we're going to do this first is because this is an extremely simple piece of code. This StrengthPipe class has no dependencies, it doesn't have a constructor that receives any injected parameters, and it's only got one method, this transform method. The transform method is a simple, stateless method, it takes in a number and returns a string. In this case, if the number is less than 10 it returns the value as a string plus the word weak. If it's between 10 and less than 20, it's the same thing, but the word strong in parentheses, and if it's greater than 20 it's the word unbelievable. To create a test for this we'll create a new file, and we're going to create it right alongside that StrengthPipe, and we'll call this strength.pipe.spec.ts. It's customary with tests to write the spec file using the same exact name, but just adding the word s-p-e-c as another piece of the test file. And it's also customary to put it into the same directory. That way it's easy to see whether or not a piece of code has a test for it, and if not we can then write tests for it if we wish. So let's start with our describe. We're going to name this after our class, which is StrengthPipe, and then our callback function, and within here we can write our first test, and our first test is going to be that it should display weak if the strength is 5. Then a callback for the test itself, and in here we'll construct a brand new StrengthPipe, and let's import that class, we've constructed it, again, it's easy to construct because it receives no parameters, and then we can write our expect statement. I'm going to put a little blank line in here because I like to see a little bit of visual separation between the arrange, the act, and the assert. In this case our range is constructing the pipe and our act can actually go inside of the expect statement. So let's expect that pipe.transform, passing in the value of 5, is going to equal the string 5, space, and then in parentheses, weak, and I do need to fix up here, I forgot my fat arrow. So we could if we wanted, extract this line of code out onto its own separate line so that we see the act as a separate piece of code, put the value, and then put it inside of the expect, like this. But I think, in this case, it's okay to make it a little more concise and just put the two lines of code together, just because the call to pipe.transform is pretty small. It doesn't make this line of code too long, so I like leaving it right here. And I'm going to save this test, and that will cause Karma to see the new test, pick it up, and automatically run it for us. And if we head over to the browser that Karma's running, we can see that it's picked up that StrengthPipe test, it's executed it, and we're getting a green that we're passing this test. If we were to go back and say, change this to transform of 6, then it's going to be wrong and the test is going to fail. And indeed it does, and it tells us, hey, I expected 6, space, weak to equal 5, space, weak. So it's letting us know what test failed, and it does give us the call stack as well, although in this case, it's not a lot of help. But that's okay because the failure is pretty obvious what the issue is and we can go back and fix this back to a passing state. We'll save that and we're back to passing. Let's write one more quick test, in this case it should display the word strong if the strength is 10. And we could pretty much just copy this code here, paste it into our new test. This time we're going to transform the value 10, and then we'll give 10 here and the word strong. And just to make sure we're doing this right let's go back to our original pipe and look, and yes, then it should be displaying the word strong. So let's save that test, go back to Karma, and now it's ran both tests and both are passing. We could of course write a third test to make sure that we're going to display the correct thing, if the strength is 20 or greater, and we would definitely want to do that for good test coverage to test that case as well. But in this course, we're not going to be worried about good test coverage, our goal is to learn how to test and not to test every piece of code in the sample application. And so now we've seen how to write isolated tests for a pipe.
Testing a Service
In this section we're going to learn how to test a service. We're going to test our MessageService, because again, this is fairly simple of code. It has no constructor, it doesn't take in any dependencies, it simply manages an array of messages. So let's create our spec file, message.service.spec.ts, which will place it directly alongside of our message service, and we'll start with a describe, we're testing the MessageService, that's the name of our outer describe, and let's define the MessageService variable, and we can type it, which automatically imports the MessageService type for us. And let's set up a beforeEach function for this test, in which we will initialize our MessageService. This lets us make sure that we've always got a brand new message service in each test. So we're going to start with a first test, in which we're going to test the initial state of our service. It should have no messages to start. Now in this case we have a little bit a problem, our arrange is actually up inside of the beforeEach, in which case we're kind of violating that tell a story rule. Let's just move forward for a minute and then we'll come back and revisit this idea later. So I'm just going to write an expect statement, and I'm going to expect that the service.messages.length is 0. I'll save that test, we'll head over to Karma, and it's picked up our new test and it's green. So that's passing. Let's add another test. In this case we're going to test what happens when we add a message. It should add a message when add is called. And so here we can call service.add message1 is going to be the message that we add. And we'll expect that the service.messages.length is 1. We'll save that test and just head over to Karma and make sure that it's passing, and yes it is passing. Now let's go back and talk again about the story of these tests. In the test we just wrote we have our act here on line 15, and we have our cert, but we don't have our arrange. In our previous test we don't have either the arrange or the act. So in this case it might actually be a pretty good idea to go ahead and move the initialization out of the beforeEach and put it inside of the tests. We are going to be duplicating some code, but again, we're making it a little bit more obvious what's happening. This is fairly arguable just because it's so simple to construct the message service, it doesn't take any dependencies, there's nothing complex going on in there, but I wouldn't think too much of if we had left this initialization call up in the beforeEach. Yes the code is hidden from us, but since it's such a simple line of code and the tests are fairly obvious in their description as to what's going on that it may not be such a big deal, but for now let's just leave them as they are. We're going to write one more test, and we're going to test the final method here on the MessageService, which is the clear method, in which the messages are reset. So let's duplicate this third test, but we'll change the description in that it should remove all messages when clear is called. So we've initialized our message service, we're still going to leave the add because we start with 0, we want to add 1, and then we're going to clear them. So the add is actually a piece of our arrange. So I'm going to put it up next to the initialization of the service, And then I'm going to call service.clear. I've separated them out with a single blank line of code. I personally like this method, it gives me a nice visual indication as to the initial state setup, the change, and then the test of the resulting state. In the case of this test it actually took two lines of code to set up the initial state, so it's nice that we didn't confuse those two things and think that the add call was actually a bit of our act, because that's now what's actually going on in this test. In this test we need to have one message at least to start before calling clear actually makes a change to the existing state of the service. If we just create a new service and call clear, in this case we actually haven't changed the state. So we have our arrange, then we have our act, and then finally, our assert. Of course, the length is going to be 0 after we clear it. So let's save that change and go out to Karma, and now we've got all passing tests again. Of course if were to ever get this wrong and accidentally put in a 1 or leave it as a 1, we do see our failing test. We'll set it back to 0, which makes everything pass, and now we've seen how to write isolated tests for a service with no dependencies.
Testing a Component
It's time to write our first test for a component. We're going to test the HeroesComponent. This component is relatively simple, it does have one dependency, the HeroService, and it does have a few methods, OnInit, getHeroes, add, and then finally, it has delete. We're just going to test the delete method a little bit. So let's get started first by setting up our tests. We need to take note of one very important thing. The heroes component has a heroes property, which is an array of heroes. So if we're going to delete a hero we've got to have to some data already in the heroes array. And let's start by creating the test file, heroes.component.spec.ts. And of course we'll start with a describe, and then the callback, and then let's define a variable for our component, it'll be of type HeroesComponent, which does import the HeroesComponent, and then we need some heroes, so I'm going to create an array called HEROES. I'm just going to define it here, I'll initialize it in a beforeEach. Inside this beforeEach let's initialize our HEROES array, I'm just going to paste in some sample data to make this quick and easy. Heroes array, it's got three different superheroes inside of it. And the final step I want to take is to construct a HeroesComponent. So we'll set component = new HeroesComponent. And already we've got a problem. It's expecting a HeroService. We're writing a unit test so we don't want to provide the actual HeroService, let's look at the HeroService. I'm going to close that left panel and open up HeroService. And you can see right here on line 26 that the HeroService makes an HTTP call. Of course, in a unit test we don't want to make an HTTP call, so we don't want to use the real HeroService. Plus, since this is a unit test we don't want to test two units at the same time, we only want to test one. Therefore, we need to fake HeroService. Now one question we might ask ourselves is, what if we just provide a dummy object, like an empty object? Is that going to allow our application to work? Well already we can see that we're getting a red underline, that there's an error, and if we look at it, it says that it's not assignable to type HeroService, but let's actually open up the HeroesComponent and see if we're just testing the delete method, are we going to have a problem? Here's our HeroesComponent, and if we go down to the delete method we can see that it actually does call a method on the HeroService, deleteHero. So no, we can just provide an empty object as the HeroService because it's going to error out when it tries to call the deleteHero method. In order for this test to work we're going to need at least a deleteHero method on the object that we pass into the constructor of our HeroesComponent, and that is where mocking comes in.
Mocking to Isolate Code
In order to construct our HeroesComponent correctly, we need to pass in an object that looks like the HeroService. again, if we look at the parameter list the HeroesComponent constructor is expecting a HeroService. This is where Jasmine comes to the rescue, by helping us create a mock object. I'm going to start up here and just define a variable called mockHeroService, and down here I'm going to initialize that mockHeroService, and set it to a call to the global jasmine.createSpyObj. This creates a mock object that we can control. We can tell it what methods it has, what those methods should return when they're called, and we can ask it what methods were called in a test. When we create a spy object like this we do have to pass in an array of method names. If the object has no methods we leave it blank, but in this case, in our HeroesComponent we are using the addHero method, the deleteHero method, and the getHeroes method. So we're going to tell Jasmine that those are the three methods that we need, getHeroes, and you can see I've just created a string element with that method name, and then addHero, and finally, deleteHero. This call will create an object that has three methods, getHeroes, addHero, and deleteHero. So now we can pass it in to our HeroesComponent constructor, and our IDE no longer complains at us that this is not the right type. And now we can move onto actually writing a test for our HeroesComponent. Since we're testing the delete method, I'm going to create a new describe, calling it delete, and the callback, and now we can create a test, it, let's check that when we remove a hero it actually removes the hero from the heroes list. That's what the delete method does here on line 36, it removes the passed in hero from the heroes array. So we're going to check that it should remove the indicated hero from the heroes list. In order to do that we do need the heroes property to be populated with the sample data that we created in our test. And that's going to be very simple. We can simply set component.heroes equal to the HEROES sample data we created. Then for our act we can call component.delete, passing in one of those heroes. Let's pass in the last one. So, pass in HEROES, and index 2, which is the last hero in that array. And finally, we can set an expectation, we expect that component.heroes.length is going to be 2. It was 3, we've removed a hero, so it should be 2. Let's save that test as it is right now and we'll head over to Karma and see what's going on. All right, we've got an error, it cannot read property subscribe of undefined. Let's go into our code, and in the delete method, let's look at where we got the call to subscribe. That's here on line 37. It's calling the deleteHero method and then it's trying to subscribe to the result. Of course, the deleteHero method returns an observable, which we subscribe to, so we need our mock object to return an observable when deleteHero is called. In this case, since we're just subscribing to that result and we don't care about the data, it doesn't really matter what that observable contains inside of itself. So, let's go up here and add in one more line, and we're going to tell the deleteHero method that we want it to return an observable. We do that by calling mockHeroService.deleteHero, and then we call the and property, which is special because this a mock object created by Jasmine, and we can tell it to return the value that we pass into this method call here. We want an observable, and the simplest way to create an observable, in my opinion, is to call the of method and pass in a true. And let's import that of method, and we're going to import that from rxjs. That imports the rxjs of method, which creates a simple observable that has the value that we pass in to the method call. In this case, it's an observable that has one value, which is true, and that will totally satisfy the requirements of this test. Again, we don't care what's coming out. So, if the value true comes out of that observable that's just fine. Let's save that test as is, go back to Karma, and now all the tests are passing. So it does indeed remove the indicated hero from the heroes list. If we were to change this length check toBe 3, of course our test is now going to fail. And yes it does, it expects toBe 3. Now you could argue, let's set this back to 2, that in this test we're not really checking that the indicated hero was removed from the heroes list, we're only checking that at least one hero was removed from the heroes list. So we could write a better expectation in this case, we could check that the heroes array still contains the heroes from indexes 0 and 1, and doesn't contain the hero from index 2, and that would be relatively straightforward. So I'll leave that up to you, if you want to write a better expectation that checks that only that one hero was removed, and it was the correct hero, then that would be a reasonably exercise to do, but I'm just going to leave this test as is here in this course because we have successfully created a mock, we have seen how to tell that mock to return a specific value when we call a method, and we're able to run our test successfully.
Testing Interactions
We have now written one test for our delete method. We have checked that it is removing a hero from the heroes array. If we go into the HeroesComponent and look at the delete method, we can see that that is what happens on line 36. That's where we remove the indicated hero from the heroes property of the HeroesComponent. But what this test doesn't check is that line 37 is correct. In line 37 we are calling heroService.deleteHero. This is an important part of this component because it actually persists the change that we've made on the server. So let's write a test that checks that this method is correct. This kind of a test is going to be different than the test we just wrote. What we wrote before was a state-based test, we checked that the state of the component had changed. We did that by checking the length of the HEROES array. So we've tested that the state of the component has changed. If we want to test line 37, the component itself, its state is not going to change when that line is called. The component stays the same. What we need to do is check that deleteHero was called with the correct parameter. And that is called an interaction test. We're going to check in our test that a certain interaction happened between the class that we're testing and some collaborator class, in this case the HeroService. So let's write our it statement, it should call deleteHero. So again we need the mockHeroService to return an observable, and we do need data in the HEROES array, and we do need to call delete. So I'm going to go up here to this test, and I'm actually going to copy all these lines of code, just paste them in here. So here we already have a fair amount of duplication between these two tests. But that's okay. Even though there's a lot of duplication we are telling the story inside of these tests. Now, if we want to get a little picky, we could look and say, is it really important that we know that we are returning an observable of true? So, is this line important? I would argue that it is, it's important to have it here in the it function and not move that to a common beforeEach. What about initializing the HEROES array? Should that be inside the it function or should that be up in the beforeEach? That one is a little bit more debatable, I could see that one going both ways, but I'm fine having it here inside of the test. It's a short live code and it does let us know that we have initialized it with some data. One thing that is important to note is that in our component we actually have the ngOnInit function. This is a lifecycle event. This happens when the component is initialized. And that calls getHeroes, which in turn calls HeroService. Ultimately that's what sets the HEROES array. Since we're doing an isolated test, the Angular framework is not running, and it's not automatically initializing this component. It's not constructing it or initializing it. We're the ones who construct the component ourselves, right up here. So if we wanted the OnInit lifecycle event to fire we would have to call it manually ourselves, doing something like this, component.ngOnInit, which we totally could do, we might write some tests for the OnInit method. But for our purposes we don't need that to happen, we can just initialize the HEROES array to the sample data that we created, and that's just as good as calling OnInit. So let's move forward, and we are going to write an expectation, I'm going to expect that mockHeroService.deleteHero, and I'm not going to call that method, I'm just going to set that as an expectation, and I want to check that it was called, and there's a special matcher that Jasmine provides called toHaveBeenCalled. And with that line of code I make sure that deleteHero itself was called. Let's save that test and we'll go and check out Karma, and all of our tests are green. If we were to go into our code and comment out this line of code right here, and go back to Karma, we do see our failing test, deleteHero was not called, so that expectation failed. If we didn't run that test, which we can do by putting an x in front of the it, changing it to xit, and then save, Karma will skip that test, and notice that everything is green. It does show that the test that we commented out was not run, but all the tests are passing, but of course this method is no longer correct, it's not persisting the data to the service. So the other test that we wrote, the should remove the indicated hero from the heroes list, that test is running and we did inside of that test say, hey, when deleteHero was called we want to return an observable of true, but if the deleteHero method is never called, that test doesn't fail, we haven't checked that deleteHero was called. So we need this other test to check that deleteHero actually was called. Now, one thing that you may notice, let's remove the x, and go back into our HeroesComponent, put this line of code back in, and we'll save both of those changes. One thing you might have noticed is that all we're doing is checking that deleteHero was called. Looking at the code again, the deleteHero service is not just called, it's actually called with a specific parameter. So let's modify our test so that we check that it actually is called with the correct parameter. And that's a fairly small change instead of toHaveBeenCalled we can say toHaveBeenCalledWith, and then we can pass in an object. In this case it's the hero at index 2 and the hero sample race. So could pass in HEROES at index 2. Now let's just save this change and see what happens, go back over to Karma, and our test is now passing, we are now indeed checking that the deleteHero method was called with the correct parameter. So, we should probably change the name of our test to should call deleteHero with the correct hero, or something like that. Now we could go one step further, we could check that not only are we calling deleteHero with the correct value, but we could also check that we are subscribing to the result of the deleteHero call. That would probably be another test, and I'll leave that up to you if you want to write another test for your HEROES component, and check that it is indeed subscribing to the result of the deleteHero call, that would be a great piece of homework for you to do. But we have now seen how to do interaction checking and how to check that methods are called inside of expectations, how to assert that the class that we're testing does indeed interact with its collaborators or its dependencies in the manner that we expect.
Summary
We have seen in this module that isolated tests are pretty straightforward. We're just testing JavaScript code. If you've already done any testing of other kinds of JavaScript code, what we learned today is probably a review for you. All we have to do is figure out what code we're going to test and write tests that correctly exercise that code. And it doesn't matter whether we're testing a pipe, a service, or a component, they all test very similar, they're just classes that have methods and we write tests for them. We also saw that mocking is quite necessary, both so that we can easily provide mock objects when other code is run that we don't care about, and when we want to check interactions between the class that we're testing and other dependencies or collaborators.
Shallow Integration Tests
Introduction
Hello, I'm Joe Eames, and welcome to this module on Shallow Integration Tests. Integration tests let us test not only a component, but its template as well. This could be a huge benefit when working to assure that your components actually do what they're supposed to do. In this module we will be learning how to do integration tests with Angular, which gives us a way to test a component with its template. We'll start by looking at the TestBed, which is the main testing tool provided by Angular. After that, we'll look at how to ignore child directives in our shallow integration tests, then we'll look at how to test out templates, which is the core reason for writing integration tests. Finally, we'll look at how to deal with child components and directives, instead of ignoring them.
Debugging Techniques with Angular and Karma
Now before we get into the actual writing of tests, I want to talk really briefly about debugging tests in Angular when using Karma. There are a lot of various techniques and tools that you can use to debug tests in Karma, but there are two practices that we're going to follow in this course that I recommend that you do in general in your own tests. The first one is to open up the Karma window, and inside of that window actually open the console. On a Windows box you do that using Ctrl+Shift+I, on a Mac I believe that's Cmd+Shift+I, and there's also a way to access it through the menu. Once that's open I'm going to make sure I go right to the console, and keep this open all the time. There's actually a reasonable number of issues that can show up where the output inside of the console window or inside of the regular Karma browser window doesn't actually give you enough information and having the console open will actually tell you some important information. So I like to keep the console window open all the time when I'm doing unit tests in Angular. The second technique helps us deal with an issue that has to do with the way that Karma and a special library that it uses called zone.js and Angular unit tests interact. Under certain conditions the actual exception that's causing the unit test can be hidden by zone, and rather than worrying about possibly experiencing this issue as we write out unit tests, we're just going to make a change to the way that run our unit tests so that this issue doesn't occur. We do that by going back into our code and going into our package.json, and I want to modify the test command. Instead of just running ng test I want to run ng test with a specific flag. I'm going to turn off the source maps. I'm going to save that and then I'm going to need to go back out to the command line and relaunch Karma. So I'll hit Ctrl+C and run npm test again. And that relaunches Karma. And then of course I'm going to open that console window back up. You might think that turning off the source maps is a bad thing to do, but it turns out that the way that Karma and zone interact on certain kinds of failing tests, this is actually the way to see what's really going on. It's possible in the future that this isn't a necessary step, but for now I highly recommend you do this in your unit testing, and we're going to use that flag in this course.
The TestBed
It's time now to begin writing our first integration test. This is going to be a shallow integration test, meaning that we're only going to test a single component and none of its child components or directives. The component we're going to start with is the HeroComponent. Let's open this up and take a quick glance at it. The HeroComponent is fairly simple, it has one Input and one Output property, and it has one method, the onDeleteClick event handler. Its template is fairly straightforward as well. In the anchor tag with a routerLink directive in line 1, a span with some bindings on line 2, and down on line 4 a button with that onDeleteClick event handler. Fairly straightforward, so let's start by creating our test file, and normally we would name this hero.component.spec.ts, but I'm actually going to name it a little bit differently. Just for the purposes of this course it's going to be nice if it's very clear to us whether or not the tests that we're writing are shallow tests, or deep tests, or just isolated tests. So for shallow tests we're going to add the word shallow here as part of the name of the test file. Now I wouldn't normally do this in production, this is just for the purposes of this course. In general, you won't be needing more than one test file for a component. You'll probably just write isolated tests or just integration tests, whether they're shallow or deep, but even if you do both you'll probably want keep those inside of the same test file. So generally, just the .spec.ts will work for your purposes. But for us, for this course, we're going to be very explicit and add in the shallow in the name of the ts file. With that we'll start off with our describe, this is the HeroComponent, and I'll put in parentheses that we're doing a shallow test on it. Now again, I would normally not put that in the description of the tests that I'm writing, but this is going to make it a little bit more explicit for us that these are the shallow tests for our HeroComponent. Later on if we came back and wrote deep tests this would help us know which one was which. And the callback function, and now we're ready to set up our component for integration tests, which we do inside of a beforeEach. Let's create our beforeEach function, and inside of here we're going to use a special utility called the TestBed. The TestBed is what allows us to test both the component and its template running together. And really what's going to happen is we're going to create a special module just for testing purposes. We do that again with the TestBed object, and I'm going to import that object from @angular/core/testing, and we can see that imported up here. Let's close that folder EXPLORER on the left. And the TestBed has several different methods, but the one that we need is the configureTestingModule. We are creating a module specifically for testing. Normally if we look at our app.module file, we create a big module that has a lot of components in it, and then providers for all of our services. This demo application is pretty small, it's only got a few components and a couple of services. Generally this is much bigger. But when we're testing we really only want to test, again, one unit at a time when we're writing our unit tests. So we're going to create a module that just has one component in it. So the configureTestingModule takes a single parameter that's an object, and that object matches exactly the layout of when we create an app module. Again, let's go back into our app.module, just to review. Starting right here on line 22, this is an object that has an imports statement, a declarations statement, and then farther down here we've also got our providers and bootstrap properties. Those are the pieces that we use when we create a module, we're going to use the same pieces when we create our testing module, although we won't worry about all of them, like we won't worry about bootstrap, and we only worry about providers under certain circumstances, we also don't worry about imports, so instead, we just need a declarations section. And inside of here we're going to add in the one component that we're testing, and we're testing the HeroComponent, so we're just going to add in HeroComponent, and that also gets imported up there on the top at line 2. Once that testing module has been created we can now create our component. And we do that by calling TestBed.createComponent, passing in the type of component we're going to create, which is a HeroComponent. Calling this function tells the TestBed to use the testing module that we created up on line 7 and to construct the HeroComponent. We're going to capture that into a variable. I'm going to go up above here and create a variable called fixture. The create component function, if you'll look at the signature, actually returns a ComponentFixture. A ComponentFixture is basically a wrapper for a component that's used in testing and it has a few other properties, more than just what the component itself has. So we're going to set that fixture variable equal to the result of the TestBed.createComponent. Once we have the fixture we can access quite a few things about the component, one of them is the component instance itself, and that property contains the actual HeroComponent. And so in our HeroComponent, we have a hero property and a delete property, if we wanted to access the hero property and see what it's value was, let's make this a little bit easier, let's give a type to the fixture variable. Again, that's s ComponentFixture, and we give it the type of component it's wrapping, which is a HeroComponent, and we need to import ComponentFixture. And now when we look at the fixture, if we hit dot, we see that we've got the componentInstance here, and off of here we see the delete property, the hero property, and the onDeleteClick method. So that's what a fixture is, and we can use that fixture in our tests. Let's write our first test. We're just going to check that the HeroComponent has the correct hero. The HeroComponent gets its hero as an input property. Since this HeroComponent isn't being run inside of another component that's going to set that hero property we're going to set it ourselves manually. So, let's cerate that test, should have the correct hero, and then let's set the hero property, do that again with fixture.componentInstance.hero, and we're going to set that equal to an object that has an id property of 1, a name of SuperDude, and we'll give him a strength of 3. And now that the hero's been set we can make an expectation on that hero property. We're going to expect that fixture.componentInstance.hero.name is equal to SuperDude. Now again, this is a somewhat silly test because we're simply setting the hero and then checking that it has a correct property, but it's a useful test just to get our integration test up and running. We'll do something more useful in our next test, but for now let's just save this test and go back to Karma and see what's happened. All right, we are getting an error. The error basically says that we cannot bind to routerLink since it isn't a known property of a, or an anchor tag. And it actually shows us the HTML, so that's a nice printout and indication of what's going wrong. What's actually happening is because we have created this testing module and we haven't given it a routing module, let's look at our app.module again. Notice in the imports we actually import our AppRoutingModule. So this module has an AppRoutingModule, and the AppRoutingModule actually contains the Angular routing module, which contains the routerLink directive. Our test module, that we've create here on line 8, doesn't know what the routerLink directive is because it doesn't have a routing module that contains it. So we will deal with that in the next section.
Using NO_ERRORS_SCHEMA
We're getting an error because the testing module doesn't know about the routerLink directive. We could try to deal with this by importing a routing module so that we have a live valid routerLink directive, but that would be a problem for a couple of reasons. First, creating a routing module is actually a little bit more complex than just importing a routing module, and two, we don't want a live routerLink because if we ever in a test want to say, click our anchor tag, again looking at our HTML, notice this anchor tag, if in a test we want to deal with this we don't want to have our application try to route in the middle of a test. There is another way to deal with this and that is to tell Angular in this test to ignore unknown attributes and elements. And we can do that by setting a special property in our testing module called the schemas property. Now this property actually exists on live modules, as well as testing modules, but we normally don't use it. We're going to give it a special value of the NO_ERRORS_SCHEMA. And you can see it's been imported up on line 3 from Angular Core. This special setting will tell Angular that for this module do not error if you encounter an unknown attribute or an unknown element in your HTML of the template, just ignore it. Once we set that if we save these changes and go back to Karma, we can see that our test is now passing. While we're at it we also see another funny artifact, the HTML template of the test that we just ran is actually showing up inside the Karma window. That's a result of testing a component that actually has a visual template. It is going to show up there at the bottom of your Karma window, but it's something that we can just ignore. Now setting that NO_ERRORS_SCHEMA in our testing module has fixed this issue, but overusing this NO_ERRORS_SCHEMA is actually a problem even in tests, because it could actually hide other issues. For example, if we go into our HTML and we mess something up, let's say we accidentally typed za instead of just a, let's save that change and go back out to Karma, and our tests are all passing. Normally Angular's going to warn us about this and say, hey za element doesn't exist, which it does not exist, at least at this time, in HTML. But because we have the NO_ERROR_SCHEMA set it's not going to warn us about this, so it won't warn us about misspellings of element names, it also won't warn us if we are misusing a directive that we want to be using, for example, ng model. If we do something wrong with an ng model, spell it wrong, or set it wrong, or something like that, the NO_ERROR_SCHEMA will generally hide those problems for us. So, it is a bit of a two-edged sword, only use this setting when absolutely necessary. And coming up in future sections we'll see some ways to deal with a test like this without using this schema.
Testing Rendered HTML
We now have a single, working integration test, but we did note that this is a fairly useless integration test. Let's write something a little bit more useful. Let's actually write a test that tests that our template is correct. We're going to write a test that it should render the hero name in an anchor tag. And we can go to our template and see that yes, inside of the anchor tag we are rendering out the hero name. So let's write that test, we do need to put a comma after this string here, and to do that we'll start off with the same start that we've got on that first test. We're going to manually set that hero property, which is exactly what would happen if we ran this component inside of another component, the exterior component would set the hero property, so we're just replicating that by setting it manually, and then we want to test that the name of this hero shows up inside of an anchor tag. So we're going to have an expect statement, and inside of here we're going to grab a hold of the anchor tag and look at the contents of it. So, we're going to start with our fixture, and the fixture has a special property called nativeElement. We were using componentInstance up on line 23, and that gets us a handle to the created instance of the component. NativeElement, this property gets a handle to the DOM element that represents the container for the template. Now this nativeElement is a standard HTML DOM element. This is the same API that you would use inside of plain old JavaScript if you were manipulating the DOM. If you're familiar with that API you'll know that elements have a querySelector function, and with that function we can grab an element by its tag. So we want the anchor tag so we can just pass in the string a, and that will get us a handle to the anchor here on line 1. Once we have that, there's another DOM property called textContent, which just takes all of the inner text and ignores the HTML and appends it all together, so everything inside of the anchor tag, which would be the hero.id and then a space after the span, and then the hero name. And we can check that that string contains the string SuperDude, that's going to be somewhere inside of that string. Now, of course, instead of using toContain I could use something like to equal, and check the exact string, which would be 1, space, SuperDude, but writing the test this way is actually keeping our test from being unnecessarily brittle. This way, no matter how we manipulate the HTML, if we decide that we want to hero name before the span, or we want something else after the hero name, like an exclamation mark, if we change that that doesn't break our test because the text still would contain the name of the hero. So this test, the way that it's written is a little bit less brittle than doing something like checking the exact string of the text content. So let's save the test and go out to Karma, and see what result we get, and we get a failing test, and all it says is that it Expected, and all we see is an opening single quote, and on the next line another single quote. It's formatted that way because those are the delimiters for a string, which in our case happens to span multiple lines. But ultimately, what it's saying is, we're getting an empty string, but it was expecting something that contained the word SuperDude and that's not what we got, instead we got an empty string. The reason we got an empty string is because we did not tell Angular to actually implement the bindings. We set the hero instance, the bindings are right here on hero.id and hero.name. Those do not get updated until change detection runs. So what we actually need to do is after we have set the hero property on line 23, we need change detection to execute. And we can do that by calling fixture.detectChanges. This is another useful method on that fixture that's a wrapper of our component and its template. This tells the component to run change detection and update any bindings that may exist on the component. In our case there's that id binding and the name binding. We'll save those changes and go out to Karma, and now our test is passing. We do have the hero name inside of an anchor tag. So in our first test, all we did was check that the componentInstance property had the correct value. And you might write shallow tests that test the value of some property of your componentInstance, but the real power of an integration test is to test the actual template. So doing something like we've done in our second test where we actually grab a hold of the DOM and check that something in the DOM is correct, that's something we can't do in an isolated test.
NativeElement vs. DebugElement
We've got our test working, and in the crux of it is this expect statement where we grab the nativeElement, which is just a property that exposes the actual DOM object. Again, this nativeElement exposes the regular old browser's DOM API. So if you're familiar with that API this is great way to work with our templates. The fixture has another property we can use to work with the template, and that is called the debugElement. The debugElement is like the nativeElement, it has a way to access to root element of our template, but unlike the nativeElement, which is just exposing the underlying DOM API, the debugElement is more of a wrapper that has a different set of functionality that is very similar to the nativeElement. Let's look at what it would take to write the same expect statement, but using the debugElement. We'll route this in the expect function, and of course the fixture's debugElement is a pointer at the root of our template, and we want to grab a specific element, we want to grab the anchor tag. So, the debugElement has a query function. Notice it's got query, queryAll, and queryAllNodes. Query and queryAll are the two functions that you will commonly use with a debugElement. Query lets you get a reference to a single node, queryAll lets you get a reference to multiple nodes. The query function then takes what's called a predicate. In our case, again, we want to select the a tag. There's a special library that we need to import and it's called the By library. And we can import that from Angular platform browser, notice it gives us three different options. Selenium and protractor, those are for end-to-end tests, we want the By from the @angular/platform-browser library. So I imported that, if we scroll up we can see that it has been imported. Now that we've got the By object, it's got a few different properties and methods, but the two that we care about are css and directive. We're going to use css here. This is a function that takes in a CSS selector. If you're familiar with JQuery and Sizzle selectors you'll be fairly familiar with how the By.css function works. If we want to get an element we just give it a string with a name of the tag. But we could also select by class name, such as that, or by id with a pound sign. If you're more familiar with JQuery than you are with the DOM API then this will feel a lot more comfortable to you. We want to anchor tag, so we're just going to call By.css, passing in an a. That will give us a reference to the debugElement around the anchor tag. And again, a debugElement is a wrapper around the actual DOM node. Similar to the way that the fixture is a wrapper around a component, a debugElement is a wrapper around a DOM node. Once we have a specific debugElement we still want to grab the text content within it so that we can run our same expect statement we want to check that it contains the string of SuperDude. So here after the query function, there's another property exposed, which is the nativeElement for that debugElement. And of course, nativeElement has a textContent property. And this lets us drill down in and get a hold of the text that is inside of an element. Ultimately, this expect statement, and the one that we wrote previously down on line 29, do the exact same things. They just get to the same answer in a slightly different way. The one that we just wrote by using the debugElement, the one down below by using the nativeElement. So let's break this down one more time. I'm going to extract this into its own separate property. I'm going to say let de for debugElement = this call, and then we could do de.nativeElement. So we are grabbing a debugElement that points at the anchor tag, in fact, let's rename this, deA for the debugElement around the anchor tag, where we call fixture.debugElement, which gets appointed to the root debugElement, and then we query for the debugElement for the only anchor tag in our template. Again, looking at our template, it has a single anchor tag. So each DOM node has a wrapping debugElement node, and when we want to find a specific element we can either use the nativeElement and use things like the querySelector, or we can use the debugElement and use the query method. Ultimately they get to the same place, so you may wonder why we even bother with the debugElement. Well the debugElement, like the fixture is a wrapper around something else, but it also exposes additional properties that we may want for other purposes. In the case of this test it really doesn't make any difference, but if we get a hold of a debugElement, and that element happens to have a directive on it, again, looking at our template, we can see that our anchor tag has a routerLink directive, then the debugElement has a way to access that routerLink directive. And we'll see an example of that in the future. There are other things that a debugElement can do. In addition to just the nativeElement we can actually get a handle back to the component that this debugElement belongs to. In certain circumstances that could be useful as well. Generally, if we want to handle to a component we just ask the fixture for the componentInstance, but sometimes if we're working with multiple components we need to know the component that a given element belongs to. And so that's another scenario where the debugElement comes in handy. Let's comment out our old use of the nativeElement, and we'll save this test and go over to Karma, and we can see that our test is passing still. So there is a brief introduction to the debugElement.
More Complex Shallow Integration Tests
We've written our first shallow integration test on the HeroComponent, but the HeroComponent was really simple. If we look at it we can see that it has no constructor, it takes in no other dependencies, no services. Also, in the template, there are no child components. It does make use of one directive, but it doesn't have any child components. Let's rate some tests for a slightly more complicated component. Let's look at the HeroesComponent. We can see here that it actually has a child component, the app-hero component, which turns out is our hero component. So it has a child component, in addition to that the HeroesComponent does take in one dependency. Here in the constructor we take in a HeroService. So, this component ultimately has two different collaborators that we've got to worry about and deal with, the HeroService and the child component of the hero component. So let's write some tests for this HeroesComponent and deal with these two issues. We'll start off with a new test file, heroes.component, and again I'm going to specify that this is shallow tests. So I'm going say shallow.spec.ts. This sets it apart from our isolated tests, which we just named heroes.component.spec.ts. We could rename that for isolated, but again, these names are mostly for the purposes of this course. In general, in the file type you won't put in the type of test that it is, isolated, shallow, or deep, you'll just write a spec file and write your tests inside of there. Let's start writing our test here. We're going to start of course with a describe as the HeroesComponent, and we are writing shallow tests for it. And we know that we need to fixture, so let's create a variable for it, which is a ComponentFixture, which wraps around our HeroesComponent, and that also imports those to types. Then in our beforeEach we're going to follow the same pattern we followed in the last test, which is to configure a testing module, where we call TestBed, and let's import that .configure.TestingModule, and we know that the testing module needs the HeroesComponent at least, so let's create a declarations section, and now the HeroesComponent. And then after it's configured we can grab a handle to the fixture by calling TestBed.createComponent. We could have just copied and pasted most of this from our last test and then adjusted the types as necessary. And let's look really quickly at our HeroesComponent and see what it does so that we can figure out a test to write. Down here in the ngOnInit it calls this.getHeroes, and of course when we construct a component using the TestBed the ngOnInit will get called automatically. And ngOnInit itself calls getHeroes. GetHeroes is implemented down here, in the which getHeroes simply calls heroService.getHeroes, and then subscribes to the result and sets the value that's returned onto the heroes property, which is up here. So we initialize the heroes property and we do it using a service. So we have got to figure out how to write a test against a component that has a service as an injected dependency.
Mocking an Injected Service
We started writing our first integration test for our HeroesComponent, but we have a big challenge ahead of us right now and that is, we're getting a service injected and we need to deal with that. So let's head over to our test and let's talk about injected services. In Angular, when we have a service we have to register it with our module. We do that in the app.module, and we do that down in the providers section. That's how we register a service. Here in our test we do need our HeroesComponent to have a service, but we don't want it to use the real heroes service that the HeroesComponent uses because the heroes service itself makes HTTP calls, and we don't want to make any HTTP calls, plus we just don't want to be testing two units at the same time, we only want to test one unit at a time. Of course, when we want to draw a boundary around our unit tests we use mocks, so we are going to create a mockHeroService. We do the same thing in our testing module that we do in a live module to create a service and register it with a dependency injector, we use the provider section. And this is going to create in this testing module a provider we want to create a HeroService provider, and that way when Angular itself creates the component, Angular is going to look at the constructor for the component, see that it requires a HeroService and try to find a HeroService in its dependency injection registry. Let's see what happens if we don't handle the situation. Let's write a quick little test, should do nothing, and we'll just expect true toBe true, and let's see what errors we get out of Karma. All right, we have a template parse error, we cannot bind to hero since it is an unknown property of app-hero. So the first issue we're getting is that it doesn't know about the child component. That's actually easy to solve. We could do that the same way we dealt with the routerLink by adding a schemas and set the NO_ERRORS_SCHEMA, which also imports that from @angular/compiler/src/core. Then there's actually a small problem with VS Code's ability to import things automatically for you, we really want to import this from @angular/core, the extra parts in there were unnecessary. Let's save that change and go back to Karma, and now we're getting the error that we need to deal with, with the service, which is there is no provider for HeroService. So that's what happens if you're testing a component and the component requires some injected service and you haven't configured the testing module to handle that service. So let's configure our testing module to handle that service, and we're going to do that using the longhand way to do with a provider. If you watched my course on the fundamentals of Angular you'll be familiar with the longhand method for adding providers. Normally we just do something like HeroService, that's how we would add a provider, let's import that. That's how we add a service to a module. Let's put this on its own line. And that just says, hey, when you ask for HeroService give it the real, live HeroService. Well we don't want that, we want to use a mockHeroService. So we can use the longhand method, which is providing an object. And that object has a provide property, where we set that to HeroService, which says hey, when somebody asks for HeroService, I'll tell you what to do. And in our case we want to use a mock, so we add in a second property, useValue, and we can tell Angular use this object when somebody asks for a HeroService inside this testing module. And so what we need is a mockHeroService. Now we haven't created that yet, so let's create that, and go up and create a new variable called mockHeroService. And inside my beforeEach before the TestBed.configureTestingModule I'm going to create a Jasmine spy object. And let's look inside of our component and see what methods are called on the HeroService. It calls getHeroes, it calls addHero, and it calls deleteHero. So those three methods are what we need to mock, getHeroes, addHero, and the last one was deleteHero. With this in place, we have created a mockHeroService that looks just like the real HeroService, at least to our HeroesComponent, and we have told Angular here in this providers section that when anybody, and in our case anybody is just the HeroesComponent, asks for a HeroService, which it does in its constructor, right up here, that instead of getting it a real HeroService we want Angular to provide it with our mockHeroService object that we've created ourselves. So let's click Save on that test, go back into Karma, and now the tests are passing, although this test that we've written doesn't actually do anything. So let's make the test do something. Again, in the ngOnInit we called getHeroes and getHeroes calls heroService.getHeroes, so let's check that we correctly set the value that's returned from the getHeroes call onto the Heroes property. That's what the getHeroes method does. In the subscribe statement we get the return value of the getHeroes call, which is a list of heroes, and we set that onto the heroes property. So we want to test that that is working correctly. So instead of should do nothing we're going to say should set heroes correctly from the service. And of course right now our service doesn't do anything if we were to call getHeroes on it, so let's tell it to return some data for us, mockHeroService.getHeroes, and because it's a Jasmine spy object we can tell it what to return when it's called, so we're going to say and.returnValue. And that's an observable so we will call of, and let's import the of operator. And we need some sample data, so I'm just going to paste in some sample data. Let's create a variable for that, and inside of our beforeEach we'll initialize our sample data, so that we know we always get a fresh copy in case we ever for some reason modify this HEROES array, for every new test this will be a fresh copy of data. And I'll want to return that. Whenever somebody calls getHeroes we return an observable around that array of sample data, and in order for the ngOnInit to fire we actually have to run change detection. In Angular change detection causes lifecycle events to run. So we've told Angular to construct the component, but we have to fire change detection in order for the lifecycle event, which is the ngOnInit to actually fire. So we'll call fixture.detectChanges. Of course we could manually call ngOnInit ourselves, but that doesn't really follow with how we do integration tests, we want Angular to handle these things. And if we did call it for some reason then we were testing our template and we needed change detection to fire to do bindings it would then call ngOnInit again, and we don't want that. Now that we've got ngOnInit firing we can write our expect statement. We'll expect that fixture.componentInstance.heroes.length, we'll expect that toBe 3. And that will validate that we have indeed set the heroes property on our component correctly from the return value of the call to get heroes. We'll save that, go over to Karma, and our test is passing. If we were to say 4 here, go back to Karma, the test is failing because our heroes sample data has exactly 3 elements, so this has to be a 3. And so we have now successfully written a test that tests the component that has both an injected service and a child component. We ignored the child component using the NO_ERRORS_SCHEMA the same way we ignored a child directive, the routerLink directive, in our first set of integration tests, and then we created a mock service by using the longhand provider syntax so that we could tell Angular to give our mockHeroService to our HeroesComponent when it asks for it in its constructor.
Mocking Child Components
Now if you'll remember when we were creating our first integration tests around the hero component, we talked about the fact that this NO_ERRORS_SCHEMA has some side effects that may be undesirable, in that it will hide any problems that are in our template. If we go into our template and accidentally misspell one of our elements like buttons and save that change, Karma is not going to have a problem with this test because Angular doesn't have a problem with the buttons element. Normally in Angular the compiler as it processes our HTML will see incorrect elements and warn us about them. But because we have the NO_ERRORS_SCHEMA property set we're not getting that warning. But if we're writing a shallow test we don't want to test our child component. Back in the HeroesComponent notice that we have the child component, the app-hero, we don't want to test our live hero component in this shallow test, but we also prefer not to have other problems in our templates hidden from us. Well there is another way around this, and that is to create essentially a mock child component. So let's take off the NO_ERORRS_SCHEMA, and I'm going to up here before this beforeEach and I'm going to create a component that looks just like the HeroComponent, only it's much simpler. And I can do that by declaring the component the same way I would the HeroComponent. Let's look at the HeroComponent. The HeroComponent has the @Component, exports a class that has a couple of properties. I'm going to grab all of this and just paste this in right here. And we'll close that up. I'm going to import the component decorator and I'm going to get rid of the styleUrls and instead of a templateUrl I'm just going to use a template, and I'm going to use a very simple div element that is completely empty. I need to import the input, and for now I'm going to comment out the output. And we'll also need to import this hero type. We don't have to type this variable because this isn't real component, but we will just for kicks and giggles. Now you will notice we are still getting this one error from our editor telling me that modifiers cannot appear here. So let's save this test as is and see what happens. Over here in Karma inside of our console we're actually seeing this error module parse failed, import and export can only appear at the top level. That's because we still have the export on this class, we'll take that off, and now our error has gone away so we're not exporting the class anymore. We'll save that test and Karma is failing, but that's because it's now finding our problem in our template that buttons is not a known element. Let's fix that for now so we can see what happens with Karma if we run it again. And now we're back to the problem we saw quite a bit earlier, which was we don't know what app-hero is. When we originally had this issue we solved it with our NO_ERRORS_SCHEMA, which we had right here, but we don't want that because we don't want it hiding our other problems, so we're going to use this HeroComponent mock class that we created. So let's rename this FakeHeroComponent, just so that it's obvious that this is not the real HeroComponent, and we're going to add this to our testing module. Here in our declarations we have the HeroesComponent, let's put that on its own line and add a second component, our FakeHeroComponent. We're getting the error out of Karma that it doesn't know about the app-hero because it's not part of the module. That's the same error you'd get if you weren't writing tests, just writing your application and didn't declare your component inside of your module. So we've added our FakeHeroComponent to the module, let's save that change, go back to Karma, and now our test is passing. So we are able, in our test, to set the heroes property using the call to getHeroes from the ngOnInit and test that the HEROES array now has three elements in it, and even though our HeroesComponent has two different collaborators, the hero child component and the HeroService, we have essentially mocked out both of those. We did the HeroService with a more traditional mock object, and then we replaced the child component with a fake child component.
Dealing with Lists of Elements
So we've got our shallow integration test working with a component that has both the child component and an injected service, but our test doesn't really do much with our template, so it's not really taking advantage of the fact that this is an integration test and we can deal with our template. So let's rate one more test and that will let us see how to deal with lists of elements inside of our integration tests. We're going to write basically the same test that we've already written, except we're going to count the DOM elements that are produced. So let's look at our HeroesComponent, and notice down here on line 14 we have an li element, and that is created once for every hero in the HEROES array. That HEROES array is the property of the HeroesComponent that we've been dealing with. So, since we're using our HEROES sample data that has three elements in it we're going to have three heroes, and therefore, three li elements are going to get created inside of this ul right here. So let's write the same test we wrote before, but instead of looking at the length of the array, let's count the number of li elements that are created. I'm going to start off with the same first two lines of code, and we of course need to name this test or give it a description, we'll say it should create one li for each hero. And starting out the same we tell getHeroes what to return and we call detectChanges, which will then call the ngOnInit, and now we're going to dig into the elements and find the li elements that we need. Let's go back to the template one more time, and just make sure the li that we see here is the only li element on the entire page. So we can just count the li elements anywhere inside of the template. So expect, of course, then fixture.debugElement.queryAll this time. Before we did query now we're going to do queryAll so that we can get back a list of elements that match. Query just returns the first element that matches the selector, queryAll will return all the elements that match the selector. Again, By.css, and let's import the By command from platform-browser, and then we want all of the li elements. And then we're going to count those, queryAll actually returns an array, so we just ask it for its length property, and then we can do the same matcher toBe 3. So this is basically the same exact test that we've already written. They should set the heroes correctly, except we're actually checking this translated correctly into our template. Let's save this test and go over to Karma, and all of our tests are green. If we check the wrong number and look for only 2 li elements, of course our test is going to fail. We've got to have the right number of elements in our expectation. So again, there's a better way to take advantage of the fact that this is an integration test and actually check that the template itself is correct. Now does this mean that whenever you're writing an integration test that you should be digging into the template? Not necessarily. It's not a requirement that a good integration test actually deals with the template, but if you're going to write an integration test the template does exist, you may as well be checking that when it's appropriate. If you're not going to be digging into the template in your test you might be better served to just write an isolated test instead of an integration test.
Summary
In this module we learned quite a few things about shallow integration tests. We learned that the TestBed is the core testing utility when writing integration tests and we learned how to set it up correctly, which really consists of making a module, which is exactly like the modules that we create in our live production code. We learned about fixtures, which are wrappers around components that we create, we learned about debugElement, which is a nice wrapper around a DOM element, and finally, we learned how to handle child directives, both ignoring them and writing mock ones.
Deep Integration Tests
Introduction
Hello, I'm Joe Eames, and welcome to this module on deep integration tests. In this module we will be learning how to do deep integration tests, which gives us a way to test our components with their live children. We'll start by jumping right into writing a deep integration test. This will not be too much more difficult than what we've actually been doing with our shallow integration tests. Then we'll see a technique for locating child directives, and finally, we'll take a bit of time looking at how to use integration tests with services to get some unique benefits.
Creating a Deep Integration Test
We've written our first few shallow integration tests, now we're going to switch gears and write our first deep integration test. The HeroesComponent is actually a perfect component for this because it has a child component, the app-hero, that is pretty simple. So we're going to write a deep test against our HeroesComponent. And we're going to be able to test the interaction between the HeroesComponent and the HeroComponent, which is the child component shown here in this template. So, we're going to head over to where our heroes is and we're going to add a new test file, and this will be for our deep tests. So heroes.component.deep.spec.ts. Again, we wouldn't normally create separate files for both shallow and deep tests; if we did we might name them this way, but in general we'll just have one spec file that will have all of our tests inside of it. To get a quick start I'm just going to grab everything from the beforeEach on up in our shallow test for the same component, because that's going to give us a good start and then we'll go through this and make some changes. We don't need this fake component anymore because this is a deep test, we want to test against the real HeroComponent, that way we can check the interaction between the HeroesComponent and the HeroComponent and make sure that they're set up correctly. For example, we can check that the HeroesComponent is correctly inputting the hero to the child HeroComponent, and we can check that the HeroesComponent is correctly listening to the delete event on the child HeroComponent. We see those here in our FakeHeroComponent, we didn't even need to implement the output parameter in the FakeHeroComponent, but in the real test, this deep test that tests the interaction between these two components we're going to check that those two are set up correctly. So let's delete this fake component, we don't want to use that, we're going to leave our sample data the same, we're still going to need a mockHeroService. In a deep test, yes, we are testing more than one component, we are testing the HeroesComponent and how it interacts with the HeroComponent, but we don't want to include the live HeroService, we still want to use the mockHeroService, we'll leave that alone, and I'll hide this left panel to give us more room. So we don't have a fake component, we can remove that, but we are using the real HeroComponent, so we need to include that in the declarations of our testing module because we want the real HeroComponent to get loaded. And we'll import that. We don't want the NO_ERRORS_SCHEMA, so we can remove that, and we do need to close down our outer describe, and we also want to change the name of this outer describe, it says it's shallow tests, we're going to change that to deep, and finally to get it to actually execute some of this code let's write an empty fake test, it should be true, and we will expect true to be true. Let's save that test and go to Karma and see what happens. And we see we got an error that we can't bind to the routerLink, which is not a known property of a. So this goes back to the same error we had when we originally starting writing our first integration test for our HeroComponent. The HeroComponent in its template utilizes the routerLink. The routerLink is a directive that is inside of the router module. We don't have a route module in this test and so the routerLink is an unknown attribute. So let's solve that problem the same way we solved this originally, we're going to go into our test. I guess I can close down this shallow test as well, we don't need that anymore, and we are going to add back in our schema, and add back in the NO_ERRORS_SCHEMA. If we save this and look at Karma, our tests are passing, and so even though this test doesn't actually do anything we are now creating an actual deep integration test. We've got a real HeroesComponent and a real HeroComponent, and they are both getting created. But they aren't even getting initialized yet because we haven't called detectChanges, which triggers the lifecycle events, which will cause the ngOnInit function to fire. So instead of doing that inside of the test, let's actually trigger that inside of our beforeEach so that our components are already initialized when we get to the test. We'll call fixture.detectChanges, and of course this fixture only points at the HeroesComponent, not the child HeroComponent, but because we have called change detection on the parent component, change detection will then run on all child components. So not only will the parent component get initialized, but all child components will get initialized. Of course, at this point, we're going to error out because we haven't told the HeroService what to return when somebody calls getHeroes, which is what called in the ngOnInit. If we save this, Karma is erroring out on us because it doesn't know what the subscribe property is on an undefined object, so we just need to tell our mockHeroService that when getHeroes is called that we should return the HEROES array. Save that and go back to Karma, and our tests are back to green. Of course our test doesn't really do anything yet, so in the next section we'll actually write a real test.
Finding Elements by Directive
Let's turn our test into a real test. Instead of checking that true is true, let's actually check that we rendered out each hero in our sample data list as a HeroComponent. So we're going to say should render each hero as a HeroComponent. Now we want to go back to our arrange, act, assert, and I've kind of moved the act in this case, because we're not going to be taking any actions other than the ngOnInit, I've moved the act up into this beforeEach. So let's move that out and put that in here. And at least we've got our act, and here we could put our arrange in, which is just to construct the HeroesComponent. So I could grab these two lines of code here, cut them out of the beforeEach, and put them in here, but I kind of feel like this is a little bit of overkill, maybe just the getHeroes call could be inside of our test. So let's leave the creation of the fixture up to the beforeEach, and we'll just leave in here that we are going to use the HEROES array as our sample data, and then we trigger detectChanges, which will trigger the ngOnInit, we might even put in a comment, run ngOnInit. So that's really what we want to have happen, is we want the ngOnInit, it just so happens we make that actually fire by calling the change detection. And now, we can dig in and look at all of the HeroComponent that were created. And there's a way to do this, there's a way to actually find child elements through a directive, and we saw that a little bit earlier. The fixture has the debugElement property, and that has the queryAll, which returns an array, or a list of items, and By has a directive method. So we can actually say to the parent element, go and find all of the elements or nodes in this template that have this directive on them. What we want to find is all hero components. Notice we're using By.directive. In Angular a component is actually a subclass of a directive. It's a more specialized kind of directive. We normally think of directives as being an attribute, such as routerLink, and components as being an element, such as what we have here, the app-hero is an element, but in the inner workings of Angular a directive is actually the parent class for both attribute directives and components, such as our HeroComponent here. So if I call this code and pass in the type that I'm looking for, I'm looking for the HeroComponent, this is actually going to get a list of all of the debug elements that are attached to a HeroComponent. So this is going to get a list of every one of those app-hero elements that is ultimately created. Again, we've got three of them, so we'll have a ul, we'll have three li's and inside each of those three li nodes we'll have an app-hero. Of course, it's not going to be the app-hero node, it's going to translate that out to the actual template of the HeroComponent. So we're going to get our anchor tag with a routerLink and we're going to get a button, all wrapped up inside of the app-hero node, it's going to basically explode it out into its actual template. What we are doing here in our By.directive is getting a pointer to each of those app-hero nodes because those are the nodes that are attached to each HeroComponent. So, let's capture that into a variable, we'll call this heroComponents, and these are actually debug elements, the debugElement.queryAll method returns debug elements, so we could name this heroComponentDEs for hero component debug elements, and then we can write a quick expectation on that. We expect that heroComponentDEs.length is going to equal 3, which is the length of our HEROES array. So let's save this test and go to Karma and check out the results, and indeed the test is passing. So we have found three debug elements that are attached to a HeroComponent. Now we can take this one step further. Rather than just checking that we have the right number of debug elements that correspond to our heroes sample data, we can actually check one of them and see if something is correct. This is one of the benefits of the debugElement. So I've got in this heroComponentDEs a list of debug elements that correspond to nodes that have a HeroComponent on them. Let's grab the very first one, the heroComponentDEs at index 0, that's the very first debug element. And inside of it we can grab the componentInstance, so this is going to be a HeroComponent. If we look at our HeroComponent, the HeroComponent has a hero property. That's an input property, but ultimately it's just an actual hero. So that is going to correspond to one of the elements in our heroes sample data array. In the case of the very first HeroComponent it should correspond to the very first hero in our array. So I can actually ask it for its hero property, and I can set an expectation on that. Let's look at our heroes sample data. Our very first one has a name of SpiderDude, so I can set an expectation that that hero's name property is equal to the word SpiderDude. Let's save that and go back to Karma, and the test is passing. If we misspell this, the test is going to fail because the name is actually SpiderDude. So we are able to go from our component into its template, get a reference to one of the elements inside of the template, and then dig back down to the component that implements that element. So we started with our heroes parent component, and we dug through the templates down into a child hero component. So what we could do instead of just checking these one by one, we might check 0, 1, and 2, and we have the names SpiderDude, Wonderful Woman, and SuperDude. SuperDude, let's save that, and Karma is back to passing. Instead of doing that we could just loop through them and check each one of them. So let's write a for loop. We'll loop through each of those debug elements, and we'll do an expectation inside of there. Although instead of just checking the name, let's actually check that the whole hero object is the same object from the HEROES array. Although instead of hard coding it to 0, we're going to set it to i because that is our iteration variable, and let's save that, go back to Karma, and our tests are passing. It is checking that every hero property on every hero component matches up with the correct hero in the sample data that the parent component received and passed down into the child component. So this is great use of a deep integration test. By verifying this we are verifying that the HeroesComponent template and how it passes in the hero object, we're checking that that piece of data is set correctly and passed into the HeroComponent, the child HeroComponent, correctly. Again, this is great use of a deep test. If anybody were to come in and introduce a bug that messed up that binding then our test would break.
Integration Testing of Services
We've now created both shallow and deep integration tests for our component. We're going to move on and talk about a related topic for the rest of this module, and that is integration testing of services. Now we saw earlier on that we could test a service using an isolated test, and since services don't have templates there aren't any pieces of code when testing a service that we can't reach with an isolated test. With a component we can't actually test the template of the component, unless we're doing an integration test. With services there is no template, so an isolated test can test 100% of the code of the service. Therefore, you may wonder why would we even bother writing an integration test for a service? The reason for that is because of HTTP. Look here in our hero.service, we can see in the getHeroes method, we call http.get. So we're making an HTTP call. The HTTP interface is somewhat complex, and even though we could create a mock for the HTTP service it can sometimes be a little bit difficult to execute correctly. With an integration test we can actually provide a service, a special mock HTTP service, that has been provided by the Angular team, that gives us a lot features when writing these kinds of tests. So let's do just that, we're going to test our hero.service with an integration test, so I'll create a new file, hero.service.spec.ts. I'm not going to put shallow or deep on this, since we aren't writing an isolated test for the hero.service that's separate from this integration test, and the concept of shallow and deep doesn't necessarily apply to services, that applies more to components, but this is still an integration test. I'm just not going to add that to the name, so this is more like a normal test file name, just hero.service.spec.ts. And let's start with a describe. We're going to test the HeroService, and we're going to have a beforeEach function, and we're going to do the same thing that we did with a component, we're going to use the TestBed, let's import that, to configure a testing module. And in this case we don't need a declarations section because we're not testing any components, we're just testing a service. So we'll obviously need the providers section, and since we're going to be testing the HeroService, we'll bring that in, and import it, and let's close down our tree view. Now let's look at our HeroService really quick, and go up to its constructor. And you can see that its constructor expects two things, an HttpClient and a MessageService. We're going to let Angular provide a mock HttpClient for us, but we need to set up a mock MessageService. So let's create a variable for that, and we will create a spy object, using jasmine.createSpyObj, and let's see what methods on the MessageService we actually use inside the HeroService. And it looks like it's only using the add method. So we will create just that method on our mock object. And of course we do need to set that up in the providers section using the longhand provide: MessageService, which also imports the MessageService up there on line 3, and we're going to useValue: mockMessageService. All right, we've got the mockMessageService set up, we'd have to tell Angular now to provide our mock HttpClient. And we do that by importing another module in our testing module. So we create an import section and we are going to import the HttpClientTestingModule. And the IDE isn't automatically importing this we'll need to manually import it, and that comes from the @angular/common/http/testing module, and that's where the HttpClientTestingModule comes from. And with that imported that will create a mock HttpClientTestingModule. So our test is almost set up, there's one more thing we need to do. We need to get a handle to the mock HttpClient service so that we can adjust it and control it inside of our test. And there's a special controller for doing this that is provided again by this same module, and that is the HttpTestingController. So I'm going to import that, and I'm going to cerate a new variable, I'm going to call that httpTestingController, and I'll give that a type of HttpTestingController. And finally, after the module's been configured, we can get a handle to the httpTestingController. This is very similar to how we got a handle to a fixture, we want to get a handle to that controller, so we set a variable name, httpTestingController = TestBed. Now in this case we aren't going to call createComponent like we usually do with a component, instead we're going to call the get method. This is a special method on the TestBed that basically accesses the dependency injection registry. So if I give it a type, in this case, HttpTestingController, it's going to look inside of the dependency injection registry for this TestBed's module, and find the service that correlates to that type and give us a handle to it. So in this case, it's the HttpTestingController, if we wanted to get a handle to our HeroService this way, we'd have to create a new variable of course, then we could just pass in the HeroService type and that would give us a handle to the HeroService, or if we wanted our MessageService we could pass it the MessageService type, and of course we'd want to change this name. So that's the way to get a handle to a service. There are others ways to get a handle to a service, and we'll see that later on when we write our first test. But just know that TestBed.get is the way to get the instance of a service inside of your testing module. And so with that we've got our testing module configured, and we'll save these changes, and in the next section we will actually write a test.
Implementing a Test with Mocked HTTP
We've got our testing module configured to test a service that uses the HttpClient, and now we're ready to write a test for it. So after this beforeEach let's create our test. Now, let's go first to our service and actually see what we want to test. I'm going to scroll up here, and the method that I want to test is this getHero method here on line 48. Ultimately this does two things, it create a URL on line 49, and then on line 50 it calls http.get against the URL that it creates. So this is a fairly simple, straightforward method, it does only a small amount or work, so we'll use that as our test. So first we're going to call describe getHero, which is the method that we're testing, and within that we'll create our first test, it should call get with the correct URL. It's a great first test. Now, the question is how do we get a handle to our HeroService so that we can actually call service.getHero, which is the method we're trying to test, inside of our act part of our arrange, act, assert? We need to handle the service. So we could go up here and create a service variable, equals TestBed.get HeroService, and we go up here and create a variable. So that's one way to handle this, there is another way, delete this line and delete this line. There's actually a special inject function that is provided by Angular, and that is used as a wrapper for our test callback. So instead of passing in just an empty function, instead we can all inject, and we'll have to import that from @angular/core/testing. We can see that it has indeed imported it from @angular/core/testing and added it to that line. And then we can get rid of that empty callback function. The inject function actually takes in two parameters. The first one is a list of dependency types that we want to get a handle to. And that's an array. The second parameter is a callback function that will then receive those types. So if we want to get our HeroService, we pass that into the array, and then in our callback function we can get a handle to that service, the HeroService, by adding it as a parameter to the callback function. And I'll of course give it a type so that matches up. And at that point we now have a handle to the HeroService. Notice how we are getting the HttpTestingController up above using TestBed.get, but we're using the inject method to get a handle to the HeroService. It is a little weird to use the two different methods side by side like this, so let's put this on its own line, and instead of just getting the HeroService we can also get a handle to that HttpTestingController. So we have to pass in that type, as a second parameter, and now in our callback, which I'm going to add to its own line, in addition to getting the HeroService, we can get a handle to that controller. And notice that the order of the parameters lines up. Inside of the first parameter to the inject function we pass in first the HeroService, then the HttpTestingController, and in that same order we have parameters to our callback function, here on line 28. First the HeroService, and we named that variable service, and then second the HttpTestingController, which we named controller. So if we do it this way we don't need this line of code we can comment it out, and now we can call service.getHero, it does require an ID parameter, so let's say we pass in 4. And then of course we have to subscribe to that in order to make the Http call actually fire. And again, we're not going to have a real HTTP call fire, instead, we're using Angular's testing module, which is going to create a mock HttpClient service for us. Now before we write our expectations, let's go back to these two methods of getting handles to services. We can use the inject function or we can use TestBed.get. The TestBed.get method is a little bit cleaner. You can see how using the inject function has made the code here inside of the it a little bit difficult to read, you kind of have to visually in your mind line them up. To make them easier to read we would probably put everything on its line. And that just makes the whole thing really, really long. Now all of the sudden our test takes a lot of lines of code. So in my opinion it's a little bit better to use the get method instead. So let's delete this code here, and this code here, we'll comment this back in, and we'll create that service, get a handle to the HeroService, and add a variable for it, and then a callback function inside of our it, and now we have a handle to the service, and we also have a handle to the HttpTestingController that we can use, and that's what we'll use to create our expectations. So the HttpTestingController and the test HttpClient are a little interesting in how they work. We have called service.getHero, which under the hood on that HeroService is going to call Http.get. So we need to tell the mock HttpClient that it should expect a call to a certain URL. In this case, if we pass in a 4 the URL is going to be whatever the heroesUrl is, then a slash, then the id. If we want to see what the heroesUrl is, we come up here and we see that it's api/heroes. So the resulting URL is going to be api/heroes/4. So we could tell the HTTP mock client using the httpTestingController, and this is the instance variable here, that we are expecting a call. And we call expectOne, and then we give it the URL, which is api/heroes/4, and that will create a request. Then we can tell the mock HttpClient that Angular has created for us what data we want to return when this call comes in. Using the request object we can say req.flush, and then we set whatever data we want to come back. In this case, it's sending back a hero, so let's just send back an object with an id of 4 that matches the id we requested, a name of SuperDude, and a strength of 100. And that will actually send back that data when it sees that request. Now notice that we told the mock client to expect that call after we actually made the call. Up here on line 30, we call getHero, which actually calls Http.get, but it's not until afterwards on line 32 that we tell it to expect a call. And then later on, on line 33, we actually tell it what to send back. So it's not until we hit line 33 that that request, that observable, actually receives a value. So if we were to put some kind of a callback function up here in subscribe, and log out to the console, like fulfilled, to say that the observable got a value back, this console.log statement won't execute until after the flush is executed on line 35. It's a good thing to note the order of how things are executed when using this mock Http. Now let's undo all this change here. And we can save this test and go over to Karma and see what the results are. And we can see that our tests are all green. There's our getHero test. It's green and it's passing, but also, if we were to comment out this line of code and do nothing, go back, now we see that it's failing because it is expecting one matching request. So we are verifying that we are making the call that we requested. So we'll comment that back in, and that will make the test pass. Now let's fiddle around with this just a tiny bit. What happens if we accidentally make two calls? What if our HeroService is actually calling http.get two, or three, or four times? We can simulate that by just making the call twice. We'll save that and go over to Karma, and we're getting a failure. It was expecting one matching request, but it did find two requests. So we get a failure if it's not calling that URL exactly once. But what if it calls a different URL? What if we call api/hero/3 after we call api/hero/4? Make that change, and our test is passing. So technically this wouldn't be correct if our HeroService was accidentally making two calls, one to the correct URL and then one to an incorrect URL, the test would still show that it's passing, but there is a way for us to actually address this. And that is a special method on the controller, which is verify. And that verifies that we only got exactly what we expected. So save that change, go back to Karma, and we're getting a failure because it expected no open requests when it was done, but it found api/hero/3. So we can get our test back to a passing state by killing this extra request. Save that, go back to Karma, and all of our tests are green. So that's how we can test a service using an integration test, and how we can use the httpTestingController in order to set expected requests, respond to those requests, and verify that no unexpected requests were made.
Summary
In this module we took a look at deep integration tests. We started how by seeing how deep integration tests are just like shallow integration tests, except that we use live children components instead of fake ones or ignoring them. We then finished up the chapter by seeing how to use integration tests to test services. This gives us one big benefit, the httpTestingController, which gives us access to a prebuilt set of mocks that Angular provides for testing services that use HTTP.
Testing DOM Interaction and Routing Components
Introduction
Hello, I'm Joe Eames, and welcome to the module on Testing DOM Interaction and Routing Components. In this module we'll start by looking at how to trigger events in our components. We'll actually look at three different methods for accomplishing this in a test. After that we'll see how to interact with input boxes. And the final thing we'll cover in this module is routing components. We'll look at dealing with two aspects here. First, we'll see how to test components that use the activated route service and then we'll look at testing components that use the RouterLink directive, and how to test that they are set up correctly.
Triggering Events on Elements
We're going to write a test that triggers an event on an element. We'll do that in a new test here inside our heroes.component.deep.spec.ts file. What we're going to do is trigger an event on a subcomponent that will ultimately get raised to the compare component. Let's look at the heroes.component.html, and we can see down here on line 15 we have our app-hero component. This parent component, this HeroesComponent is listening for the delete event on the child. When that event is raised by the child, this parent component, this HeroesComponent will call its delete method and pass in the current hero. That event is triggered here in the hero.component itself. It's here on line 4, this button. When this button is clicked the onDeleteClick method is called, and then inside of the component we can see that we call this.delete.next, which raises the delete event. Up here on line 11 that delete property is a new EventEmitter so calling next will emit a new delete event. When that event is emitted we'll raise the event, which is again handled by the HeroesComponent, and so what we want to do is trigger that button click. That will cause the chain of events that will ultimately result in the HeroesComponent handling that delete event by calling its own delete method. We can see that in the HeroesComponent itself, down here on the delete method. So this is going to happen, let's write that test, it's a brand new it, do a little semicolon here, new it. And in this case, the name's going to be a little long, instead of using the regular single quotes let me use the back tick, which lets me do a multiline string. We'll say that this should call heroService.deleteHero, since that is what is actually happening. Here on line 37 we call heroService.deleteHero when the Hero Component's delete button is clicked. Let's put that down on a second line, just because it's a little bit long. And we'll fix the formatting on our callback, and we'll need the first two lines from this test because we do again want heroes to be populated into the hero property of the HeroesComponent. We could again see that here in the ngOnInit, it calls getHeroes, and down in the getHeroes method call we call heroService.getHeroes, which puts the heroes that it retrieves onto the heroes property. So by using these two lines of code we get our mockHeroService to return our sample data, and then we run ngOnInit. Let's remove this comment, we don't need that anymore. And once that's populated we now need dig down from the parent into the child in order to get a handle on that button. But we also have to remember that this parent component actually has a collection of children, we can see that here, we have one li for every hero that we have in our collection. So in our case, our sample data, we actually have three heroes. So we're going to end up with three child components. So to start off our test we're going to get a handle to all of the child components, we'll do that by creating a new variable, we'll call that heroComponents, and this will actually be a collection of debug elements. We're going to take the fixture, and we're going to take its debugElement, we're going to ask it to queryAll, which we'll get back a collection of debug elements, and we're going to use By.directive. The directive method allows us to specify a directive type, and this will go through this component's HTML looking for elements that actually have that specific directive on them. We're looking for the HeroComponent directive. And that, again, is right here on line 15. That is the HeroComponent. Now we are used to calling this a component, but it's important to realize that in this case when we're saying By.directive, a component is actually a subclass of a directive, it's a more specialized directive. So this method, By.directive, will actually find elements that have components on them in addition to elements that have those decorator directives or attribute-based directives that we're normally used to calling a directive. So in this case we're going to get a collection of debug elements that represent each of the three hero components that have been created inside of the li's here inside of our HeroesComponent. And once we have that collection then we can take the next step, which is to click that button that will raise the delete event. So let's do that on our first HeroComponent, in that collection. We're just going to grab the first one by calling index 0, and we're going to query it again, this time for a specific element. We're looking for the button. We've seen that before in the hero.component.html. Here on line 4 we've got a button. We want to find that button, it's the only button inside of the template so we're going to use query, and we're going to call By.css, and say button. Once we have the button, again on a debugElement, it has a triggerEventHandler method. And we're going to call that method. Let's put this onto its own line because it's going to be a little bit of a long line of code. This method takes in two parameters, the eventName and an object for the event. Both of these are required. So the first parameter will be the name of the event, which is a click event, we're raising a click event, and the second parameter is the object that we want to come along with this event. So we want an object that actually has a stopPropagation method. because if we look inside of our HeroComponent, we can see that it actually does receive an event object, and it takes that event object and calls stopPropagation here on line 14. So we actually need a little bit of a dummy object that has a stopPropagation method. Now we could create a Jasmine spy, or we could just write one by hand. Let's just write one by hand to see a slightly different method for handling this. We're just going to create a stopPropagation method. And this is just an empty method. So when it gets called nothing happens, and that's fine, we don't care to do anything when we call stopPropagation, we just want the code to be able to call stopPropagation and not error out. Once we've called that method, we assume that everything is correct and it's going to raise the delete event on the child, which will then be handled by the parent, which will call the HeroesComponent's delete method. So, we want to check that that delete method was called. Let's look at that method again, we can see it here in the HeroesComponent, the delete method receives a hero that is passed in. So let's not only check that it's called, let's check that it's called with the correct hero, and let's use Jasmine to spy on this method. We already have the component, it already exists, that delete method already exists, but we want to watch and see if it's called, and we can do that with a Jasmine spy. So let's go back up to the top, and we're going to call the method spyOn. This is a Jasmine method, so it looks global, but it exists because we're using Jasmine. We're going to call fixture.componentInstance, which gives us the instance of the HeroesComponent and we're going to spy on the delete method. This syntax here will tell Jasmine, find the delete method on our HeroesComponent and just watch it, watch to see if it's invoked. How this is accomplished out of the hood is very interesting, but beyond the scope of this course, so just trust that this works, and now we can create an expectation. We can expect that fixture.componentInstance.delete toHaveBeenCalledWith the correct parameter, and in our case, it's going to be that HEROES sample data, it's going to be the first hero. Because remember, we grabbed the first hero here on line 58. It was the HeroComponent at index 0, so the first HeroComponent, and that should correspond to the first hero in our HEROES array. So if we click the delete button on the first HeroComponent, then that HeroComponent's hero, which should be the first one, is passed in to the delete call on our HeroesComponent. Again, that's this delete call on line 35, the hero is passed in, and that's again done because we are binding to the event. Because we're listening to that event, when it's raised, the hero that's passed into the event is ultimately passed into the delete method. And in our test we're checking, we're listening for that delete call, and we're checking to make sure that the correct hero was passed in. Let's save this test, and head over to Karma, and indeed we have called our new test, and the test is passing. If we get something wrong, like we're expecting a different hero, the second hero instead of the first one, we'll go back and we can see that Karma's failing, it's actually getting the wrong hero. We could of course make that match up by calling heroComponents at index 1, which is the second HeroComponent, and we save that and our tests are back to passing. Or if we trigger the wrong event, instead of triggering the click event on the button, what if we trigger the focus event, and we can see that the test is failing because it was never called. So by triggering the correct event, ultimately it's calling through and we're getting the right result because it is calling the delete method on our parent component, our HeroesComponent. Let's set that back and save, and our tests are back to passing.
Emitting Events from Children
We've seen how to raise this event by actually triggering an event on the underlying HTML element, which in the child component ultimately raises the delete event, which is listened to by the parent component. There's another variation on this, if we know that we've wired up that button correctly, we can actually just tell the child component to raise its delete event and not trigger it through clicking the HTML, but instead just saying, hey, you have a delete event, please raise it. Let's rewrite our code just a little bit here in our test in order to do that. Instead of querying for the button we're going to delete all that code, we're going to take our heroComponents, the 1 at the index 1, well let's go back to the first 1, at index 0. We're going to take that and we're going to grab its componentInstance. Again, heroComponents is a collection of debug elements, and we're grabbing one of those, the first one, and if we grab componentInstance property we are actually grabbing the component class, the HeroComponent class, and let's type that as a HeroComponent. Right now, TypeScript doesn't know what type this is, so we have to give it a little bit of help. We're going to tell it that this is a HeroComponent, by putting in that code, and now if we hit our period we can see all of the public members on the HeroComponent. It's got a hero property, a delete property, and an onDeleteClick method. So it's the delete property we want to access, and we know that that's an event emitter, so if we call .emit on that and pass in an undefined, then that event is going to be raised. So let's save that code and then look at our Karma tests, and indeed the tests are still passing. So we're just telling the child component, just raise your event. Rather than clicking the button sort of manually, we are instead just telling it raise your delete event, I know that the parent component is listening for the delete event and once it hears the delete event it will still call its own delete method and pass in the correct hero. Now you may be thinking that we need pass in the correct hero here in this emit, but that's actually not true, let's go to the HeroComponent and look at where we call delete.next. Notice that when we call this event, or emit the event, we don't pass anything in here. We could pass in a value, but we're not passing in a value, it's just raising the event. The magic is here in the HeroesComponent in the binding. Here is where we're listening to that event, and we're just listening for the delete event to be raised, we don't care if there is a specific value that comes through on the emission of that delete event, we're just listening to know that the delete event has been raised, and then we call our own internal method, the delete method. This can be a little bit confusing, we might better name this handle delete, or deleteHandler, something like that, maybe deleteHero, that might be a better way to name this internal method, we'd have to go into the HeroesComponent and change that as well to make those match up. But when we listen to the delete event, we just call deleteHero and we pass in the current hero. This hero actually comes from the ngFor, it's right here where we create the hero variable. So we don't need the child component to tell us which hero is the current hero because the bindings and the HTML maintain that correlation for us. So if we pass in the hero right here it's going to be the current hero for this specific component. So again, back into our test, we don't need to pass a specific value in when we emit that event, we can just pass an undefined and everything still works correctly. So let's go back and undo the changes that we've created, we'll leave this as delete, and the name of the method will still be delete. And that's an alternate way to listen to events on child components. Just tell the child component to raise the event, and then you could test that the parent component is listening for that event and responding to it correctly. Now this brings us into a little bit of a discussion on what is better, should we trigger the underlying HMTL like we did initially in our test, where we actually grab the button and called click on it, or just tell the child component to emit its delete event? And this is a somewhat subjective call. If you have sufficient tests for the child component the test to make sure that the button is wired up correctly and then when it's clicked it raises the correct event, and there's probably not any reason to test that again here in the deep test. So on the test for our HeroComponent we would test that, but here in the deep test for the HeroesComponent we're more worried about the bindings in between the parent component and the child component, and so that's what we want to test, we don't necessarily need to test that the child component is working correctly. It's a bit of a subjective call, and it's really up to you as to what you want to do, but just keep in mind that you might be duplicating your effort, if you test everything about the child component and everything about the parent component working together inside of a deep test like this. Instead, the best part about a deep test is to just test the boundaries and how those two interact with each other.
Raising Events on Child Directives
We've seen two variations now for testing the interaction between a parent and child component, we're going to look at one third final option, just a small variation on this method here. Here we have dug down, we have grabbed the actual instance of the child component, and we told it to emit its delete event. We can actually accomplish basically the same result a little bit differently. I'm going to comment that line of code out, and remember heroComponents is a collection of debug elements, and if we grab the first element in that we have a debugElement. Debug elements have a trigger event handler, that's how we initially triggered the click event on the button when we initially wrote this test, is we used the debugElement's triggerEventHandler. But what we did was we grabbed a handle to the debugElement for the button. Here we've got a handle to the debugElement for the component, and it has again, triggerEventHandler. It has the standard events that exist on DOM elements, like click and focus, but we've also told the parent component to listen to the delete event. So when that child component raises the delete event the parent component is listening for it. We can just trigger that event directly on the debugElement. We'll pass in null, we could have passed it undefined, it's kind of the same. We're just telling the debugElement, just trigger the delete event. Rather than telling the component to raise its delete event, or to emit it, we're telling the debugElement to raise that event. So this is little bit of a shortcut for doing the same exact thing, but in this case we're actually testing even less. In this case we don't even know if the child component actually has a delete event emitter, we're just telling the debugElement for the child component to raise the delete event. So again, multiple ways to accomplish the same thing, which way you're going to do it is really up to your own preference.
Interacting with Input Boxes
We have seen how to raise an event on a DOM element, or to just raise an event on a child component. The other common DOM interaction that we'll have to deal with in tests is to type into input boxes and be able to test what happens when values are put into input boxes. So we're going to write a new test that needs that capability. Notice here in our HeroesComponent, up here above the list of heroes, we actually have an input box, and a button underneath that that when we click the button we'll actually add the hero to the list of heroes. In this case, the code that it calls is somewhat inline, it calls the add method, and passes in the value of the input box, and then it sets the value back to a blank string. So if we go into the code for our HeroesComponent and look at the add method, we can see that it takes that name string, it trims it, it sets the default strength, and makes sure the name actually has a value, and then it calls heroService.addHero, creating a new hero, and then subscribes to the return result, and the hero that comes back from the server will actually have an id, and that's put into the array of heroes. So fairly straightforward, basically two steps, we do some validation, set default values, and then persist the change to the server, once the changes has been persisted to the server we then update the client. So in order to test this add method in an integration test we really want to be able to type into that input box. So, we need to be able to put a value into the input box that we can see here on the line 5. Let's go back into our deep spec and let's write a new test for that. And in this case, the test will be that we should add a new hero to the hero list when the add button is clicked. And of course a callback for that. Now note that these techniques of raising events on elements or typing into input boxes, these are the kinds of things that we might do with both shallow integration tests and deep integration tests. We actually happen to be doing this in a deep integration test, but really this is a technique that can be used regardless of whether we're doing deep or shallow, we're just interacting with our templates. So let's start actually by getting again these same two lines of code, we'll paste them in here, and then since we're going to be creating a hero and the value that we type into the input box is the name of the hero we're going to create a variable for that name, and we'll name our new hero Mr. Ice, and let's delete this blank line here since this is all kind of part of our arrange setup. And ultimately when we add the new hero, if we look at our HeroesComponent, we're going to be calling the heroService.addHero method. And of course, we are using a mock HeroService, so we need a mock method for the addHero method. We did create the mock right up here on line 21, so we do have a mock addHero, but right now it's just an empty method doesn't do anything. When that method gets called it needs to be an observable because we subscribe to the result of that call to addHero. So we need this method to return an observable. Let's go back into our code, we'll say mockHeroService.addHero.and.returnValue, and again we're going to call our of method, but this time, instead of passing back the entire array of HEROES, we actually want to pass back a new hero. Going back to our code, the addHero service takes an object that has a name and a strength and returns back a full hero and a full hero has three properties, an id, a name, and a strength. So we need pass back a new object that has an id, and in this case we'll give an id of 5, a name, and that will be the name constant we already created, and a strength, let's just give this hero a strength of 4, and that will set up the addHero method correctly. We've also got to get a handle to the input box. So let's create a const called inputElement, which is fixture.debugElement.query By.css, and we're looking for an input element. Again, this is our HeroesComponent template, there is only one inputElement, so we can use the query to just grab that first and only inputElement. And let's grab the nativeElement, so we're not going to have a reference to the debugElement, we're going to have a reference to the underlying DOM element, which we will need in order to set the value later on. And now we can take our action. We'll call inputElement.value and set that equal to the name constant that we created. And that simulates typing Mr. Ice into the input box by just setting its value. Once the value is set, in order to kick off the process we need to trigger the click event on the addButton. We've done that before, we've seen this, let's get a handle to the addButton. We use the pretty much the same code here, except we're going to grab the button, but there are several buttons in this template because we know that the child template, the HeroComponent, actually is a collection of heroes and each one has a button inside of it. We could just stick with our query method because it should return to us the very first element that matches the selector, so the very first button, but let's be a little bit more exact, let's say queryAll, we're going to get a collection of all the buttons, but we're going to grab the very first one, we know that that is the addButton. In the template, the very first button, is the one we want. And of course, in this case, we don't want the underlying DOM element, we just want the button because we're going to use the triggerEventHandler method. Once we set the value we call addButton.triggerEventHandler, and we're going to raise the click event, passing in a null for the object because the event object doesn't matter in this case, if we look at our HTML we call add heroName and we don't use the event object. So we can just pass in a null for our second parameter, and that will kick off a chain of events, which ultimately calls the add method on our HeroesComponent, which then calls the hero service, addHero, and returns our new hero object that we create here, and that is added to the collection of heroes. Now to test that everything is okay, what we'd like to do is actually see that our new hero is inside of the list of heroes. In the template, again we have this unordered list, it has a list item for each hero, and in the template for each hero we can see that both the hero.id and the hero.name is shown. So let's check that our hero.name is inside the resulting HTML, and because Angular isn't going to automatically update the HTML when we change the underlying values, we actually have to trigger event handling to update the bindings, let's call fixture.detectChanges, and then let's check to see that the hero name is in the resulting HTML. There are a lot of different ways that we can do this, but one way to do it is to grab the fixture and grab its parent debugElement, and then we can look for just the unordered list, and grab the nativeElement, and grab the textContent from within that, and that is going to be the textContent of all of our heroes. Now there's four of them, all four of the heroes, it's going to be all of their templates concatenated together in one big string. Another way to do this is instead of grabbing all of them we could grab just the fourth one, doing what we did up above, grab a list of the hero components, grab the fourth one, and then check its text content. But in this case, because the names are unique it's okay to get all the templates and put them altogether, and check for our new hero to be inside that list. So let's create a constant for that, and let's expect that the heroText is going to contain that name that we created, Mr. Ice. We'll save that test, and go to Karma, and notice that the test has been run, and it's passing. So our component, we now know is doing everything correctly. If we add in a value in the input box and click the add button, it ultimately results in a new HeroComponent being create, added to the list, and displayed properly, after the bindings are updated from change detection.
Testing with ActivatedRoute
Now that we've seen how to deal with DOM interaction in our test, we're going to move on and deal with routing in our tests. Before we do there's something very important to understand, as we write tests for components that contain elements of routing in them, and that is that no matter what you're doing you shouldn't test the framework. Now whether that's dealing with routing or some other aspect of the framework that you are utilizing in your components, when you test those components, don't test the framework, don't check to see if the framework itself works correctly, leave that up to the creators of the framework, your job is to test your code. So always assume that the framework works correctly. Instead, what you want to test is that you are interacting correctly with the framework. This definitely comes into play when we deal with routing. We don't want to test that the routing components actually do their job, we want to test that we are calling them correctly or that we are configuring the correctly, but we do not want to test that they are actually doing their job. For that reason, when we're dealing with routing and tests, we're not going to actually let our code route. We know that Angular's router works, so we're not going to try to actually do routing, instead we're just going to test how we interact with the routing components. We're going to start off with a simple scenario. We're going to test our HeroDetailComponent. In this case, HeroDetailComponent's relationship to routing is involved with two components, the ActivatedRoute service and the Location service. So we're going to write a test against this component that deals with these two services that have to do with routing. We're going to start by creating our test, and I'm just going to call this hero-detail.component.spec.ts. Now of course, we need to start off with our describe, and inside of here of course we need our beforeEach to set up everything. We are going to make use of the TestBed, so this will be an integration test. So we'll import that, and we'll configure the testing module, we'll start off with our declarations, and the HeroDetailComponent is what we want. That automatically imports the HeroDetailComponent for me thanks to Visual Studio Code, and going back to the HeroDetailComponent we see that it's got three services. So we are going to need to tell Angular what to actually provide when this component requests those three services. Let's close that left window, and so we are going to need to provide three services, we'll need our providers, and we're going to create three mock services. Going back to our component, again, one for the ActivatedRoute, one of the HeroService, and one for the Location. We're just going to mock these three services. When a user visits this page the route has an id in it, and that id is used in order to obtain the correct data. So we can see that the API for the route is fairly complex, route.snapshot.paramMap.get. So we've got to mock that out. The HeroService, let's look at that one real quick. We call getHero, we call updateHero, and that's all that's called on the HeroService, and finally, let's look at Location, and we just call back. So HeroService and Location will be easy, let's create those mocks first. We'll create the variables for them. And for mockHeroService, we'll use jasmine.createSpyObj, and again, the HeroService just uses two methods, getHero and updateHero, so we will mock those two methods. And Location, same thing, jasmine.createSpyObj, and in this case we only use a back method, so, we'll create the mock that way. And finally, let's look again at the ActivatedRoute, and notice, again, that this API is fairly complex, the route object has a snapshot property, the snapshot property has a paramMap property, the paramMap property has a get function. Now we could use Jasmine for this, but it's actually a little bit simpler to create a mock object by just doing some hand coding. We're just going to create an object, and that object is going to have a snapshot property, and that snapshot object has a paramMap property, and that paramMap is going to be an object that has a get method, and we're going to create a dummy method for that, and in this case we could tell it to return whatever we want. The get method is used, it returns a string. Now if you're not familiar with this syntax, this little plus right here is used to turn that string into a number. So, get will return a string, we are going to return the string 3. If we created this as a Jasmine Spy object it'd be a little bit easier to configure our tests, but for our purposes, having it just return the string 3 every time is going to be just fine. And so now we need to set up those mock services using the longhand for the providers. We start with our ActivatedRoute, we tell it to use our mockActivatedRoute, and two more, the HeroService, which we need to import, and that returns mockHeroService, and that Location, which returns the mockLocation. Now one thing to note, there actually is a location global variable when you're dealing with the browser. So, notice that we haven't imported Location from anywhere, we have no Location service. That is not actually the service that our hero detail is using. If we go back and look at what the hero's detail service is using, this location type is actually from @angular/common, so I'm going to grab that import, and include it here in my test, just to make sure that we are telling Angular's dependency injection service that right token, so that our component will get the correct mock service when it asks for its location dependency. And with that, we can actually now create our component. We need a fixture variable, I haven't created one, let's go up here, and add fixture. And after the configureTestingModule, we can set the fixture to TestBed.createComponent, same thing we've done several times before, passing in the HeroDetailComponent type. And so now we have mocked these two routing services, the ActivatedRoute and the Location service. We didn't use some built-in mock type like we used with HTTP, we're just, by hand, mocking these services, which was actually pretty straightforward. Next we need to write our test, and so in the next clip we will take care of that.
Dealing with ngModel
And now it's time to write an actual test, so we'll go after our beforeEach and we'll create a new it function. We're going to call this should render the hero name in an h2 tag. The point of this test is just to check that the hero name is rendered our correctly. This is a very basic test, it's just checking the initial rendering. If we look at our HeroDetailComponent, it does have a Hero property, and that Hero property, if we look at the template, is rendered into this h2 tag right here. So we're just going to check that that name ends up inside of the only h2 tag inside the template. That's fairly straightforward. We've got our fixture already set up, we do have the mockHeroService that's very important because it does need to provide the test data. So let's actually do this here in the beforeEach. We're going to set our mockHeroService, the getHero method, because again, that's what returns the actual data that is going to go into the Hero property that's right here on line 28. So we need that getHero service to return an observable that has some data in it, we'll tell it to return an observable using that of function. Let's import that. And the data we want it to return back is just an object, a hero object, so we can construct that by hand. It needs an id property, we'll give an id of 3, a name property, we'll give it a name of SuperDude, and we'll give him a strength of 100. That will return the proper data, and now we can just check the rendering. Of course, to check rendering, we do need to initialize change detection so the bindings are updated, and we're not getting our IntelliSense, let's fix that really quick, we're going to give a type to this fixture. The createComponent function returns a component fixture with a subtype of the actual component contained within it, so let's go up to fixture, our fixture variable here, we'll create a separate line for these two, and we'll give this a type of ComponentFixture, with a subtype of the actual component, HeroDetailComponent, and we'll need to import that ComponentFixture type, and that's added it right up here to line 1. And now, we should get IntelliSense. And we got detectChanges, we'll call detectChanges, and that's basically our act, and now we can set our assert. We're going to expect that the fixture will go dig into the nativeElement, and we will look for that h2 using the querySelector, and then we'll grab the textContent property. I could have used the debugElement and the query function, but since I need to get to the textContent and that's on the nativeElement anyway, it was just as convenient to start with nativeElement. And we'll check that it contains the name SuperDude. And notice that in the template it was all uppercased, we can see that right here, so we're going to need to look for SUPERDUDE, all caps. And let's save that, and head over to Karma, and our test is failing. And if we look at the error message we can see that it's failing because we can't bind to ngModel, since it's not a known property of input. If we go back into our template, we can see that we got the ngModel right here on line 6. The problem is the ngModel comes from the forms module. And if we look at our test module, which we created right here, we're not importing the forms module. So this module has no idea what the ngModel directive is. The quick way to deal with that is to just import the forms module. Unlike the routing module the forms module's a very straightforward import, we don't have to configure anything, so, let's just add an imports, and we're going to import the FormsModule. And that did automatically import the FormsModule as an ES6 module as well, right here, and with that change we can save this test and go back to Karma, and the test is now passing. So that's how to write a simple test. We've dealt with mocking the ActivatedRoute, and we dealt with the ngModel directive inside of one of our tests.
Mocking the RouterLink
We've tested a component that uses an ActivatedRoute and saw that that wasn't too difficult. We're now heading back over to our heroes.component.deep.spec file, and we're going to deal with the routerLink. Let's go into this test and we're going to come down on the line 31, we're going to comment out our schemas line so that we are no longer using the schema, we're using the default schema, which will check for any unknown elements or attributes. Save that, go back to Karma, and lo and behold, our tests are now failing. And the failure is that we can't bind to routerLink since it isn't a known property of an anchor tag. That's because the template for the child component, the HeroComponent, has a routerLink in it. We can see that by going to the HeroComponent, its template, and there is our anchor tag with the routerLink on it. So let's deal with this routerLink directive in a new way. We're going to create a fake routerLink directive. I'm going to back into our test, I'm going to go up here before the beforeEach, and up before my describe, and I'm going to create a new directive. And I'm going to call this RouterLinkDirectiveStub. Let's import our directive decorator, we'll give it a selector of routerLink inside of square brackets, so that we'll look for the attribute routerLink, and we are going to need a host property, I'm going to come back to that in just a second. The RouterLinkDirectiveStub itself needs to take in the parameter that is set, the value of the routerLink attribute, and we do that with an Input property, and we just give it the same name as our directive, routerLink, and we'll call this linkParams, and give it a type of any, and then what we want to do is watch the click event on the DOM element that this routerLink is on. And when the click event is fired we want to capture the fact that is was clicked and store what the routerLink's path was actually set to. Again, it's this string value here, we want to know what that value was actually set to. That way we can determine if the routerLink was configured correctly. So we're going to create an onClick method and we're going to create a navigatedTo property, I'm going to put that up here, and set it to null, and inside of here in the onClick, I'm going to set that navigatedTo property equal to the linkParams. So this will do two things. One, I can check that it's even been clicked because if navigatedTo is null then it hasn't been clicked, and two, if it has been clicked I could check and see what the value actually is to know whether it's correct or not. Now in order to have this onClick method fire when the anchor tag is clicked, we need to do a host listener, we're going to listen to a host event, and we do it like this. We're listening for a click event, and that's the name of this property inside of this object, and the value is going to be the method we're calling. So we're telling this directive, listen to the click event on the parent DOM node, and when that event is fired, call my onClick method. Now before this will work we need to add it to our modules, let's go down into our testing module, and after the HeroComponent we're going to add that RouterLinkDirectiveStub, and let's save that change, and go back over to Karma, and our tests are back to passing. So, this RouterLinkDirectiveStub was actually built inside of the official documentation as an example of creating a directive stub that you can use to listen to the RouterLink directive. I did not come up with this myself, but it gives us a nice template for dealing with built-in directives where we can write our own stub for that directive, and then be able to test that we have configured that directive correctly. Of course, we do have to implement the interface correctly, the routerLink has a nice simple interface that just takes in the params of that string, and it has basic functionality about being clicked. And next we're going to utilize this RouterLinkDirectiveStub to actually write a test.
Testing the RouterLink
Now that we've got our RouterLinkDirectiveStub created, we can write a new test that tests that we have configured our HeroComponent correctly. We want to know that we have configured our usage of the routerLink correctly. This allows us to fix that in places, somebody comes along later on, ourselves or another developer, and makes a change that breaks that routerLink, the test will fail, and we'll know that something's wrong. So let's write that test, a brand new test down here at the bottom, and we're going to call it, it should have the correct route for the first hero. Remember this is a deep test, we've got a collection of heroes, so we're just going to check the route on the first hero. And really, when we're checking the route on the first hero, going back to the template, the route is /detail/, and then the hero.id. If we look at our first hero, in our sample data, it's got an id of 1. So the correct route for that hero would be /detail/1. That's exactly what we're going to test, we're going to get a hold of the routerLink for the very first hero, and we're going to check that if click on it, that the navigatedTo property is set correctly. So let's start off by again grabbing a handle to our heroComponents collection, again that's the collection of directives, the HeroComponent directives, we'll get our debugElement.queryAll, and we're going to query by directive, and we're looking for our HeroComponent, and that'll give us a list of all of the debug elements that have a HeroComponent, we know that there's three of them, and now we're going to get the routerLink directive for the first hero. So, I'm going to say let routerLink = heroComponents at 0, index 0, so it's our first HeroComponent, and I'm going to call the query method to find by directive, the RouterLinkDirectiveStub. So that will give me the debugElement for the anchor tag that has the routerLink on it. Once I have a handle to that debugElement, I actually want a handle to the actual directive, the class of the directive. Remember when we wrote our RouterLinkDirectiveStub we created a class and it has the navigatedTo property, we want a handle to that class, so I'm going to call .injector.get, which will give me a directive on the debugElement, I've got to pass it the type, which his again RouterLinkDirectiveStub, and that will return back to me the instance of the RouterLinkDirectiveStub for this specific HeroComponent. Once I have that done I want to click the anchor tag, so go back to heroComponents and go to the first one, and we're going to get the anchor tag. This one is By.css in this query call, we're just getting the anchor tag, and we can trigger an event handler, we're trigging the click, we don't need to pass along an event object, so I'll pass in null. So that has clicked the anchor tag, which should set the navigatedTo property to the correct value, and let's set our expectation that that's exactly what happened. We're going to expect routerLink.navigatedTo toBe, again, it's going to be /detail/1, that's the correct URL. And as always, we do need to set our initial data and trigger change detection, so let's go up here and grab just these two lines of code to give us our initial setup. And now if we save those changes and head over to Karma, we can see that we have indeed got another test, and that test is passing. If for some reason we had the URL incorrect, we were expecting it to be /detail/2, then our test would show it. If we had changed our configuration, for example, if someone had come in to the template and added in /detail again accidentally, the test is now failing because the URL's incorrect. So this test guards us against configuring that routerLink incorrectly. See that change, and we're back to a passing state.
Summary
In this module we learned a couple of things. First, raising events requires a little bit of digging around to find the right element, but otherwise, the code is straightforward. And we looked at two different ways to deal with events and integration tests. First, we looked at how to trigger events on a DOM node, and second, in deep integration tests we saw how to emit events from child components. Then, we saw how to deal with input elements in our test. This is straightforward since we just set the value property directly. And finally, we finished up by writing tests around the routerLink directive. We wrote our own stub for testing that is was configured correctly.
Advanced Topics
Introduction
Hello, I'm Joe Eames, and welcome to the module on Advanced Topics. We'll cover two main topics in this module. First, we'll see how to deal with asynchronous code in our tests. With observables in Angular this isn't as common of a need as it used to be, but it can still come into play. Second, we'll see how to use the CLI to generate code coverage reports. Both of these techniques can be very valuable if used appropriately.
Adding Async Code
Since in this section we're going to be showing how to test asynchronous code, the very first problem we need to solve is the fact that our project doesn't have any asynchronous code in it. Yes, there are observables, but as we've shown in previous tests we can make the observables act synchronously, observables don't have to be asynchronous, so therefore, none of our tests had to deal with any asynchronicity. We want to change that, so we're going to add a little bit of a debounce to our save function here. Since the save function is called when somebody clicks save, we can solve a little bit of a usability issue by adding a debounce. So if somebody were to click the save button a bunch of times in very short order, we don't fire off a whole bunch of requests to the server. I'm just going to paste in a basic debounce function. Take a moment now to copy this code and add it into your project if you're following along. You'll probably want to pause the video in order to do that. There's no more reason to go over the debounce function, other than the understand the fact that a debounce function lets you make sure that another function doesn't called too often. So in our case we want to debounce our HeroService. We can do that by calling our debounce function, and we pass in a callback function, and instead of that callback function we're calling our actual code for our save function, and the second parameter to our debounce function is the time we want to wait. We want to wait 250 ms. So if somebody were to click save more than once inside of a quarter of a second, the server is still only going to get contacted once. And it takes in a third parameter if we want it to execute immediately, we don't, we want to wait at least the 250 ms. The call to debounce actually creates a function, which you want to just execute immediately, and we invoke it right there, and now we've wrapped the functionality of save inside of a debounce function. But this does makes the code asynchronous. Essentially this is just a timeout, which we can see down here on line 54, so again, we do have asynchronous code inside of our project. And next we write a test for this code.
Basic Async Testing
We've added some async code to our hero detail, let's save those changes, and we are going to add a new test to our hero detail tests. This test, we will call should call updateHero when save is called. Very straightforward test, just an interaction test where we check that if we call the save method then we should ultimately call updateHero on the heroService. As long as that gets called then Save is doing its job. And to implement this test we'll add in a callback. We need to tell the heroService to return an observable. We do need to set up our mock inside of our code, we are calling heroService.updateHero, and then we're subscribing to the return of that, so we need to tell the mockHeroService to return an observable when updateHero was called. We'll do that pretty straightforward, mockHeroService.updateHero.and.returnValue, we'll use the of function, and this time we'll just pass back an empty object, since inside of our code we actually ignore the return value. So therefore, an empty object coming back is just fine. Save that, we also need to call change detection, and that will get our component in the correct state. Our action, of course, is to call the save method. Let's just call it directly, we won't worry about actually clicking the button, we're just going to call save on the componentInstance, just like we would do in an isolated test. And finally, we need to assert that the test was correct. In this case we're going to expect that mockHeroService.updateHero toHaveBeenCalled. We're just checking that updateHero gets called. So let's save that test and go back to Karma and see what we get. All right, we can see that the test is failing, and it's failing because it thinks that updateHero wasn't called. Yet if we look at our code, it's pretty obvious that if we call save here on line 44 then over inside of our code we're going to call the updateHero, so long as the debounce function works correctly, which it does. This debounce function will just wait 250 ms and then call updateHero. So the problem is, is our test is synchronous, yet our code is asynchronous. So the test is executing, yet 250 ms after the test is done it's actually going to call updateHero. Now we could try to address that by waiting at least 250 ms before we call our expect statement. So let's wrap that in a setTimeout. And in this case we'll wait 300 ms and then we'll click save and go back to Karma, and now our test is passing, but we're getting a false positive, it's passing for the wrong reason. If we go back into our code and comment out the call to heroService.updateHero, go back to Karma, the test is still passing. The reason is our test is still a synchronous test, so it's not waiting around 300 ms for the expect statement to run, the test is just executing and then moving onto to the next test. We need to make it asynchronous, and there's a way in Jasmine to do just that. We can add a done parameter to our callback, the second parameter of the it function. And if we do that then Jasmine knows that this is now an asynchronous test, and it will wait until we call that done function before it finishes up the test. So we can go inside of our setTimeout and call done after our expect, and now the test will wait until this asynchronous code has run. We'll save it, go back over to Karma, the test is failing because updateHero wasn't called. go back into our code and fix it, save it again, back out to Karma, and now it's passing and we know that it's passing for the right reason. It's passing because the code actually gets called. Now let's talk about the problems with this test. We have introduced a 300 ms setTimeout. What if we wrote 5 or even 10 different tests around this save method, we might have 10 different setTimeouts each waiting 300 ms. That means we've got a total of 3 seconds that our test suite is now sitting around doing absolutely nothing, just waiting for some asynchronous code to run. So our test suite is getting slower and slower, and in unit tests you don't want them to be slow, you want them to be fast. We're going to address that problem next.
Using the fakeAsync Helper Function
We've written our first asynchronous test and we've encountered two basic problems. The first one we've already discussed, which is that we're making our unit tests take even longer. Every time we write another one of these unit that we've written here we're going to add about a third of a second to the running time our unit test suite, that's undesirable. On top of that, the code is a little bit more difficult to read. We've got this setTimeout added in the middle of our test, we've got this done function we have to call, so the code in our test is less than ideal. Thankfully, because we're using Angular, there are actually some tools we can use to get around these problems. So we're going to delete this extra code that we've written, and we're also going to delete our done function. And we're going to use a utility that Angular provides, which is the fakeAsync function. The way that the fakeAsync function works is we actually wrap our callback for our test, this callback that we're creating right here, we're going to wrap that in a call to fakeAsync. And fakeAsync we need to import, so let's import that. It comes from @angular/core/testing. If we scroll up to the top and look, we can see that we have imported this fakeAsync function. So back to our code, we wrap our callback inside of the call to the fakeAsync function. So we'll parentheses there, and a parentheses down here, and the fakeAsync function itself returns another function that is then passed to the test. With that fakeAsync wrapping on our callback function we can now treat all of our asynchronous code as if it were synchronous, and essentially control the clock while our test is running. So, remember again, inside of our component, we are waiting 250 ms before we call updateHero, that is what our debounce function is doing. But we can actually tell our test to fast forward 250 ms in between our call to save and our expect. And we do that with another function called the tick function. The tick function also comes from the same place, @angular/core/testing, we're going to import that, and the tick function we pass in a number of milliseconds, it's an optional parameter, we don't have to pass anything in, if we don't it will just tick forward 0 ms, and there are reasons to do that. So we'll pass in 250. And with this code, what's going to happen, is our test is going to call save, then it's going to tick forward 250 ms and call any code that should be called inside of that timeframe. In our case, it's the callback from the debounce function. And then, we're going to call our expect statement. The reason we can do this is because Angular itself runs inside of zone.js, and the fakeAsync function makes this code run in a special kind of zone, that zone.js will create, that allows us to essentially control the clock inside of that zone. So we can tell it to tick forward 250 ms. If you're not familiar with zone.js and what it does, it's outside of the scope of this course, but just understand that Angular is kind of controlling the execution environment through that special utility, and using these integration tests causes our code to also run inside of zone. So now our test that was asynchronous is now synchronous. If we save this test and go over to Karma, we can see that our code is still passing. And we're not getting a false positive here. Let's go back to our code and comment out the call to updateHero. And the test is failing because it wasn't called. Let's comment that back in, save the change, but let's go back to our test, and instead we'll only tick forward 240 ms. Going back to the test, the test is failing because the code isn't called. Back to 250 ms, and the test is passing again. So using the fakeAsync function we're creating a special zone that allows us to control the asynchronicity of our code, and treat it as if it were synchronous. We can basically treat this callback function, right there, as if it were executed synchronously and not asynchronously after 250 ms. And that makes our test much simpler. The only thing that's a little unique is this call to tick, and the wrapping fakeAsync, but the test itself is far more readable, and once you to understand what fakeAsync is and what tick does then you have no problem reading the code at all. Now what if we don't know exactly how long we're supposed to be waiting? Well there's another utility for that, and that's called flush. Flush, again, comes from the same place, we have to import it. And the flush call basically says, look at the zone and see if there are any tasks that are waiting. If there are waiting tasks go ahead and fast forward the clock until those waiting tasks have been executed. So by changing from tick 250 to flush, and saving that test, go back to Karma, and the test is still passing. In this method we're telling fakeAsync to work with zone.js and figure out what tasks are waiting to be run, and just execute them all so that everything is done. Sometimes there's value in using tick, but oftentimes flush is just a simpler option. So that's how we can control an asynchronous code and be able to treat it synchronously.
Using the async Helper Function
We've looked at two ways to deal with asynchronous code inside of our test, first we used the built-in Jasmine done function, then we used the fakeAsync function, which works with zone.js to make our asynchronous code act as if it were synchronous. There's another utility that Angular provides for us, and that's the async function, which is similar to fakeAsync, but works a little bit differently. What async is built for working with is promises. So let's go back into our code, and we're going to change our save function a little bit. Instead of doing a debounce, let's just comment this whole save function, we're going to create a new save function. And inside of here we're going to create a Promise, we're just using the built-in JavaScript promises, create our callback, which takes in a resolve parameter, and then we'll call our actual code, which is right here, and then we'll resolve the promise. So this code is a little bit different than the debounce function, but this is still going to be asynchronous. Now with Angular we don't use promises by default. In AngularJS we used promises a lot, but you still might be dealing with other third-party libraries that use promises. So, changing the save method around to work this way sort of stimulates as if we were using some kind of a third-party HTTP library that dealt with promises, or some other third-party library that deals with promises, the difference being, promises are always asynchronous, even if they execute immediately they will be asynchronous. So if you have any promises in your code you have to deal with asynchronicity. Let's save that change and go back over to our test file, and we're going to get rid of this test, I'm just going to comment it out, and we're going to create a new test. Again, the same name, I'll just copy this name here. And then instead of fakeAsync we're going to use the async function. This again comes from Angular, and it's essentially using the same mechanism under the hood, it's relying on the fact that Angular uses zone.js. If it weren't zone, neither fakeAsync nor async would work for us. So again, the callback function is wrapped inside of our call to async, we do need to import the async function, and need to be careful, there's an async inside of the queue module, we want to get it from @angular/core/testing. We're going to have the same basic starting code that we had here, in the original version of our test, and we do want the same expect statement, but this time we're going to ask our component to wait until it has stabilized. And the component understands when it sees a promise inside of itself that it hasn't yet been stabilized until that promise resolves. So again, inside of our component we've got a promise, inside the save function we can call a special function on the fixture, which is whenStable, and that call returns a promise that only resolves when all promises inside of the component that are outstanding, or pending, when all those promises have been resolved. If you're not familiar with promises they basically just use a then function, and that's what's returned from whenStable, and we pass a callback in here, and then inside of the callback is where we're going to call our expect. And therefore, this code, line 57 is not going to get called until any promises that are pending have been resolved. So really, again, this is relying on zone.js, I did kind of say that it's any promises within the component, but ultimately, it's zone that knows about these promises. Zone is wrapping around the execution context, so zone understand when a promise is pending. And this whenStable call gives us back a promise that's only fulfilled when all other promises have either been fulfilled or rejected. If we save that code, go back to Karma, we can see that our test is passing again. So it's a little bit different method. I generally prefer fakeAsync when I'm testing asynchronous code, the async function will handle promises just fine, but it doesn't deal well with setTimeouts. We couldn't use the async function to test when we were using the debounce function, we can with a promise, but not with the underlying setTimeout. On the other hand, fakeAsync works with either. So, let's comment out this test, and comment this test back in, and if we save and go back out to Karma, we can see the test is still passing. FakeAsync can work with both a promise and a setTimeout, and pretty much all other asynchronous types of code, whereas async really is only capable of working with promises. So it's up to you when dealing with asynchronous code which of the two methods you prefer to use, but my own personal preference is to default using fakeAsync.
Code Coverage
The final thing we're going to do is look at how to generate a code coverage report. Thankfully, because of the CLI, generating code coverage reports is fairly straightforward. Now you either need to have the CLI installed globally or you'll have to add an npm script to your package.json. If you've got it installed globally all you have to do is type in from the command line ng test --code-coverage. If you don't have it installed globally you can just put this command into a new npm script command, such as gencode, and then you would run npm, space, run, space, gencode. I'm going to go ahead and execute this command, and you can see that basically it just looks like it's run Karma for us and executed our tests. In reality, it's done more than that. I'm going to terminate the running process, and we're going to look at what's in the directory. And you can see that there's a new coverage directory here that didn't exist before. This is created by that command I just ran. Let's go into our code, and let's open up the coverage directory, and check it out. And you can see inside of here that there's a lot of different files, but the only one that really matters is this index.html file. I want to open that up in a browser, you can open it up however you want, but the simplest way is to just double-click on it inside of your Finder or Explorer window. When you do that, it'll open up a browser window that looks something like this. We can look at this and get an understanding of how much of our project is actually covered by test. So, for example, let's look at our app/hero directory. And inside of here we've got the HeroComponent. We can look at that and see that our onDeleteClick event is not being covered by a test. This is great, it lets us know that we need to write a test for that event. We'll go back in our browser a couple of times, and we can of course drill around and look at other directories and other files and see how well they're covered. But there is a drawback currently to this method of looking at coverage. Let's go in our project, and notice that we've got a hero-search directory, and inside of that is the hero-search.component. Now we haven't written any tests for the hero-search.component. So if we go into our code coverage report, we'll also notice that there is no hero-search directory listed. If we go into src/app, we just see the files that are inside of that directory. So what's going on is that this code coverage report doesn't even know about that file. So therefore, it's not even getting considered. As a result, it doesn't tell us that we're not covering it with any tests. So file has to be at least loaded and run in order for the coverage report to know about it, and therefore, report on it. There are some ways around this by loading up all the files, but it's an extra work, so just understand that there are some limitations to this code coverage report, but in a more production-like project you'll probably have less of issue that it doesn't point out files that aren't covered, but it'll tell you how many lines within your files aren't being covered, and that will be very valuable to you. And that's how we can generate code coverage reports using the Angular CLI.
Summary
In this module we went over some advanced topics. We saw how to deal with async code in our tests. In addition to the built-in method that Jasmine uses we also saw how to deal with async code using the fakeAsync and async functions that Angular provides. These functions take advantage of zone.js to make our async code work synchronously. Finally, we saw how to use the CLI to do code coverage. This generated a report that we could view for ourselves. There are a few issues with this, but it is still very helpful.
Course Summary
Thank you for watching this course. I hope you got a lot out of it. Before you go, I want to mention a few key takeaways. When writing your unit tests you should prefer isolated tests. They should be your default test because they are so performant, they're not very brittle, and they're easy to write compared to the alternatives. But sometimes, you want to test the template. In that case, use an integration test, but again, only when needed. Between shallow and deep integration tests, prefer shallow ones. Use deep tests rarely, and only when you're convinced you need them. If you enjoyed this course, jump on Twitter and say hi, but most of all, thanks for spending your time with me.
Course author
Joe Eames
Joe has been a web developer for the last 13 of his 16+ years as a professional developer. He has specialized in front end and middle tier development . Although his greatest love is writing...
Course info
LevelBeginner
Rating
(74)
My rating
Duration3h 20m
Updated6 Sep 2018
Share course