8.3 Component Unit Test Preparation
In this lesson we'll see how to prepare a unit test file for components so that we can start to add our own unit tests. You'll see that we need to bring in dependencies, as we would for a service test. I'll also show you how we can mock one of Angular's own classes for use in our test.
1.Introduction6 lessons, 42:00
2.Get Started With Angular7 lessons, 42:38
3.Core Concepts7 lessons, 55:20
4.Template Deep Dive11 lessons, 1:10:56
5.Forms10 lessons, 1:45:41
6.Routing9 lessons, 1:15:10
7.Using the HTTP Client5 lessons, 56:24
8.Testing10 lessons, 1:23:27
9.Building for Production1 lesson, 03:40
10.Conclusion1 lesson, 01:32
8.3 Component Unit Test Preparation
Hi folks, in this lesson we're going to see what steps we need to take in order to get a component test ready for us to start writing unit tests. At this point, the tests for components should run, but they will fail because of missing dependencies. So let's get the game component ready for us to add some tests for it So let's just focus on the GameComponent tests. And let's see what happens when we try to run the tests. So the example test, which the CLI added to the component for us, has failed, and it has failed with template parse errors, because the GameComponent uses an instance of the controller's component, but the test doesn't know what that is. So first of all we need to fix that. So we'll need to import some test helpers first. We'll need the HTTP client testing module that we used in the last lesson, and we'll use the router testing module this time as well. So we can import a special module that makes the router behave during test just like we imported the special module for HTTP. And we can now add these to an import array in the configure testing module method in the first before each year We'll also need to import the constant service, player service, and game service, and inject these using the provider's array. This should fix all of the missing dependencies. So let's go back to the test browser and see what's happening there. And we can see that we've still got the template parse error, and that's because we still haven't bought in the app controls component yet. So nothing has changed as far as the test output is concerned. So let's import the controls component And as that's a component, we can pass it to the test module using the declarations array. And let's go back to the browser now and see if importing the controls component has fixed our tests for us. And we can see that the test is still failed, and we know that some kind of error event has been thrown, but it's a little bit cryptic. We don't really know much about what failed. Let's have a look in the console and see if anything illuminating in that. And we do have some errors. And we see that there is something that failed to load. So something is trying to load something over an AJAX request and that has failed, but we don't really know what that is. Fortunately, it's quite easy to actually get to real error which is preventing the test from running. So let's just stop the ng test command. And let's run the command again, but with a special flag. By disabling the source maps, we should get a better error message that gives us more of a clue as to what the actual problem is. So this time we can see that the actual error is that something cannot read the property hand of undefined. So the reason for this is because the component's templates references the player's hand. So what we need to do before the component is even initialized is create a player with a hand. >> So the boilerplate for a component test is slightly different than that of a service. So let's just take a look through the setup quickly. So one immediate difference is that the first beforeEach wraps the beforeEach callback function in this async method. The reason for this is because the test calls the compile components method ater configuring the test module, and compile components is asynchronous because in order to compile the component, the template needs to be loaded, which usually happens asynchronously. There is a caveat here, however. Because we are using the CLI to run the tests, the module and component and everything else is pre-complied by the CLI, so we don't know if they need to invoke compiled components. So let's get rid of that. And now because we aren't calling compiled components, we don't need to run the first beforeEach asynchronously. So let's remove the async method and the import as well. We already have two beforeEach methods in this test. As you can see, the test has these component and fixture variables at the top, and then inside this second beforeEach, the value of the fixture variable is set to using the return value of calling create component. This method actually creates an instance of the component and it returns this fixture. A component fixture is a test harness which helps us work with the component in the test. After getting a hold of the fixture, which it gets from the test harness, the last line of the second beforeEach calls detectChanges. This method tells Angular to bind any data properties in the component's template. In order for us to fix the problem we have with our tests, we need to create a player before we call this detectChanges methods. That should fix our problem. So component is the component that we're testing. In this case, that's the gameComponent. So we need to create a test dealer as well as a test player because the game component uses the player and somewhere in the code it gets the second player from the players array. So there needs to be two players in the player's array for that to work successfully. So let's go back to the browser now and see if we've made any progress. So we can see that the error message has changed. This time, it can't read the property path undefined. And we know from the second line here that the error is coming from the controls component. Let's just open up the controls component. The only place where we are using path in this component is in the switch statement in the ng on init. So we could fix this, because the activated root property, which is in the property root is public. Now it shouldn't actually be public. We dont need to access the root from the template of the controls component, and we don't need to access it from a different component or service. So what we really should do is make the route private. But this presents a problem for the test because it means we can't access the route property of the controls component from the test, and we need to access it in order to update it. So we could just break the rules and make the route public and the world probably wouldn't end, but that's not the right way to do it. So what we need to do instead is to mock the activated route so that the snapshot does have a URL segment with a path in it. This mock will only be used to testing, and it's mocking an internal angular class. We're likely to want to use the same mock, or at least a similar one, in other components as well, not just this component, so we should put the mock somewhere generic. Let's create an _mocks folder and put it in there. And let's create a new TypeScript file in this folder. And we'll call this new file, activated-route.moc.ts. So we don't need to do much in this class. So our activated route mock class has a single property called snapshots. And in the constructor, we can set this to an object which has a URL property, and the value of this property is an array containing a single object that has a path property. Great, so now we want to use Angular's dependency injection to inject our new mock. So we need to import the mock and we'll also need to import the thing that is being mocked. So now in the providers array, we can tell Angular that wherever the activated route class is requested, it should use our mock instead. So we saw a very similar example of using this object in the provider's array when we were working with the HTTP interceptor earlier on in the course. The difference there is that we instructed Angular to include our own interceptor wherever the HTTP interceptors injection token was used. And that's why we set the multi option in that case to true. In this case, we are not setting the multi option to true. So what Angular will do, wherever the activated root is provided, it will use our mock instead. So it won't add the mock to the activated root class, it will substitute the activated root class with our own mock class instead. So let's save this file now. And let's go back to the test browser. So we can see now the example should create test is running and parsing, so we're ready to start adding our own tests for the component, and we can do that in the next lesson. So in this lesson, we saw how to prepare a component for unit testing so that it's ready for us to add our end tests. We saw how a component test differs from a service test in that it has to create an instance of the component, using all dependent services and components. And we saw that sometimes we might need to set up some data structures ready to be used for the test, such as how we had to initialize players in this particular example. We also had a important lesson on how we can create mocks of classes, and have Angular inject these mocks into services instead of the real classes. We do this by using an object in the provider's array. And using the provide and use class keys to substitute the real class for the mock class. Thanks for watching.