8.2 Unit Testing Services
In this lesson I'll show you how to add unit tests for one of the methods in the game service. I'll also explain how we can achieve basic test features like spying on methods to check they are called with the correct values.
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.2 Unit Testing Services
Hi folks. In this lesson, we're going to see how we can unit test our services. We got the spec file for the game service into a state where it runs, in the last lesson. So now, we can start to add our own tests to the file. So, I like to add a describe for each public method in the file. So let's add one for the startGame method. Now we can test, what the method actually does by adding its inside this describe. So one thing the method does is create a new deck and save it, in the deck property of the game service. So this should be super easy to test. First of all, let's just make sure there's nothing hanging around in the deck property of the game service. Now let's invoke the start game method. And now let's check that the deck property is defined. And let's see if the test is passing. And we can see, that it is. So, I've set the test up in a very specific way. I've got three individual lines of code that I've separated with line breaks. And the reason why I've done this is because unit tests should follow a pattern known as the AAA pattern. Each test should be broken into sections which arrange the conditions for the test, that's the first A, act, that's the second A, and finally make an assertion, which is the third A. So triple A tests, I've divided my tests up into different sections. The first one is the arrange section, so in order to arrange the conditions for this test. The only thing that we need to do is delete the deck property if it already exists. So then we can act and in order to act, all we need to do is invoke the method that's being tested. In this case, that's the start game method. And lastly we make an assertion. And we do that using Jasmine's expect method, so the thing that we expect is the deck property and what we are expecting is that that is defined. So we use a matcher, which is a special method of Jasmine code to be defined. And that just tests that the thing that we want to assert is defined. And the test is passing. So we know that it is defined. So we know that the start game method does create a new deck, awesome. Another thing the start game method does is use the transform method of the pipe on the cards inside the deck using the face cards pipe. We can also test this, but it will be slightly more complicated. So let's add it, a new it for this test. So it, is a Jasmin method by the way just like describe is. And let's just make the window slightly wider so that we can actually see what's going on without this whole full down breaking it. So what we need to do is watch the transform method of the pipe and make sure that it gets called. So as well as the global describe and it methods that we making new soft. Jasmine also add, say global function called spion and we can use that to watch methods. In order to check that they get called and see what parameters they get passed. There is a special place that we should add spies however. We shouldn't add spies inside a test, so we shouldn't add them just in an it method, and we definitely shouldn't add them in a describe. So what we need to do is add them using another global function called before each, and this will get invoked by Jasmine before each test. So, let's add it to the start game describe. We don't want to add it to the outer describe with the other before each methods, because then it will get evoked before every single test in this whole file. We don't want to do that. We just want to spy on the pipe within the tests for this start game method. So we can add to put 4H, to the nesting describe and it will only get evoked for the methods inside this describe. So the Spy On method takes two arguments. The first is the object which contains the method that we want to spy on. So in this case, that's the pipe. And then the second argument is the name of the method that we want to spy on in string form. So the method that we're gonna be spying on is the transform method, which all pipes have. So when we add a spy, Jasmine will basically wrap the method that we're spying on, and this will cause the method not to be called. But in our case, we do want the method to be called. Because we store the return of the transform method in the cards property of the deck. So, in order to get Jasmine to actually invoke the method that's being spied on, we just need to call through to the original method And we can do that using the call through method, which we chain onto the N method. So there's a number different methods that we can do here. We don't always have to call through, sometimes we might want to call a Different method, a fake method. And in that case we use, an doc cool fake, instead. But in this case we just want to call through to the original method. So now we need to invoke the start game method once again and check whether the transform method gets called. So the transform method itself has two arguments, which we can see on the left hand side of the screen here. The first argument that gets passed Is the cards, which is an array and the second argument is the easy mode property of the game service. So first of all let's set easy mode to true. And now we can invoke the start game. And now we can invoke the start game method. And now let's check that the transform method was called. So the first argument is an array, at this point we don't necessarily we don't care what is in that array. So we can just check that any array was passed as the first argument. And we set the easy made property to true, so true should have been passed as the second argument. So the test is structured in the same way, we've got three distinct sections. We've got an arranged section, which in this case we just set the easy mode property to true. We then got an act section, where again we're just invoking the start game method. Which is the method under test. And lastly we make an assertion and that's the transform method of the pipe, was called with an array and the value of true. So let's go back to the browser now, which should still be running in the background. And we can see that all second test this past, excellent. So what else do the start game method do? Well, it calls the shuffle and deal methods of the deck. Testing these presents a challenge, because we can't spy on the methods. In each test for the start game method, we want to invoke the method, that's the whole point. And each time we invoke the method, a new instance of the deck is created. So if we were to try and spy on methods of the deck, those spies would be lost each time we invoked the start game method. What we need to do instead is test indirectly for other things that they affect. So the deck starts out in order with all of the spades in order and then all of the hearts in order and then all of the diamonds in order. And lastly, all of the clubs in order. So to test the shuffle method has been called, we can just check that the deck is not in this order. Let's import our own instance of the deck into the test. And then we can compare the deck in the game service with a fresh instance of the deck that we've just imported. So we can't just compare the two arrays because arrays are different instances of objects. And so they will never equal and so the test would always pass if we just compared the game service.deck.cards to the freshdeck.cards. So what we can do instead is stringify each of the arrays of cards and just check that the strings aren't equal. So we've saved the test, so the test should have rerun in the background, let's go back to the browser now and see if our test is passed. And we can see that it has, so we know the deck is being shuffled as it should be. This should be fine for our needs, so what now? Well the start game deals cards to both players. So again, this gives us an easy way to indirectly test that the deal method does get called. So we first create, two new players. We then invoke the start game method, and we then assert that each player's hand Is two in length. Let's go back to the test browser once again. And we can see tha'st all four tests are now passing. And, we can see that the new test is passing also. So lastly, the start game method sets the game started property to true. This should be incredibly easy to test. So we first set the game started property to false explicitly, we then call the start game method. And we can then check that the game started property is equal to true. And let's just check the output, and we can see that test passes also, perfect. So now the start game method is completely tested. So what I would like you to do now is carry on and write some additional tests for the player turn, dealer turn, and reset game methods. You should be able to fully test each of those using only the things we've looked at in this lesson. Feel free to leave the get number fact method for now though. We'll come back and see how to test observable based code later on. Just remember to take that f off the outer describe when you're done. Otherwise, these tests will also run when you try to f describe other files. So in this lesson we saw how to unit test a service. We saw how to get a reference to the service being tested using the inject method, to get Angular to initialize the service correctly for us with it's dependency injection. And we saw how we can write basic unit tests using Jasmine to test the things that the method does internally. Thanks for watching.