In a proper unit test we want to isolate external dependencies as much as possible to guarantee a reliable test outcome. Http calls represent such external dependencies. Therefore, when testing our Angular components or services that rely on data retrieved via Http, we need to make sure to mock such calls and instead provide some fake data during the test execution. In this lesson we about the new
HttpTestingController that has been added in Angular v4.3.1 to make our life easier.
We have here a people service, which is an Angular service, which uses here the HttpClient from the Angular common HTTP package. Here whenever that fetch people method is being called, it executes a get request to the server. What we would like to achieve now is to test this method.
If you can see, there isn't much logic here to test. For the purpose of this example, we would like to see how we can mock out such an HTTP call such that it is not being made to a real endpoint, but rather to some that we control within our tests.
This is a normal practice, especially in unit tests, while in integration tests you might even want to call the real back end. Let's give a look at this people service pack TF file, which I have already created.
This is a standard testing setup in Angular. On the very top, we import some classes like a test, but eject from the Angular core testing. We'll see in a second what that means.
We import all the people service, and here we have a simple Jasmine test set up where we have here the overall test suite. Then we have a simple test here where we say it should fetch a list of people, and here inside, I basically do an expect statement which will break, of course.
Now let's execute the test by running the npm test. In the Angular CLI, that should be already set up to call the NG test underneath, and as you can see, it fails as we expected. Whenever we change that statement here, it refreshes and should succeed correctly. Great.
Let's now start by testing here our people service. The first step here is to set up a before each statement, which is used basically as a setup phase for each test that is being run, so before each of these if statements being executed before each step is being executed. That's normally the phase where we set up our stuff.
In this case, we actually use the test bed to configure our testing module. Just like in a normal Angular application, you have to have a module where you attach your services. Within Angular unit tests, you attach them basically here to that so-called test bed.
Just as in normal modules, we have an import statement where we attach basically the module, and then we have a provider section, just as you probably expected, where we register our people service.
To get an instance of our people service below here in our test case, we can use that inject keyword up here and wrap the whole test case basically with that inject, and then inside here, we can specify the first parameter which will define the types which we are expecting. Then inside here, we specify the variable name of our service within our test case here, and also the type for type without completion.
At this point, we could say expect people service to be defined. You can see that we get a couple of errors up here, or that no HTTP client has been defined and so on. That's basically a problem because we didn't actually import here any HTTP module which our people service internally expects, as you can see.
For that purpose, the new HTTP client introduce a so-called HTTP client testing module. We can directly import that from that Angular common HTTP testing, and now we can reference that one down here and save again. Now, you can see how the test successfully passes.
The next step is to actually test our service. Rather than obviously to basically assert here that a service has been defined, we can actually make the call.
We can call .fetchpeople, and then we can subscribe and say here where the people arrive, and we expect here that the people is an array. The length should be two, and let's also say that we expect that the people array, the first person's name to equal Yuri. Great.
We have set up our expectations. The next step is to actually mock that call which will be made inside our people function. If you go inside there and fetch people, we see here it makes a call to that API, and so we want to mock that one.
In order to mock that API call, we can import here HTTP testing controller, which again, can grab here. That's the controller, and we call it here HTTP mock of type HTTP testing control.
Further down here, we can say now const request is equal to the HTTP mock we've just ejected. Expect one call to that API, we want people callback there. We can even give him a description here at this point and say call to people API.
Now, we can also assert that request. We can say the request that is being made should be of type get. Once you have set up those assertions, we actually have to provide some response to this call here. Because right now, we have set up the mock, but we actually didn't even respond to that fetch people API such that our expectations here match.
For doing so, we can do a request flush, and we see here we return an array where the first person's name is Yuri. Then let's say there are some other names, Igor. Great.
Finally, we say httpmock.verify. If we save again, you can see how the tests still pass. Now, to verify whether we are actually testing something, we could here change the API to see that the tests again break here just as we expect.
Of course, you may have noticed that in this case, the test isn't really meaningful, because what we are testing here is that HTTP mock property works. If you want, at the maximum, we are testing that the API endpoint here is the correct form of the re-execute you get instead of, let's say a post request and so on.
The core essence here of this lesson is actually to show how we can mock out such requests, so assume that here we don't just have the HTTP call, but we have some subscriptions which process the standard result and so on.
That way, we could test that logic without effectively calling a real endpoint, which we would have to set up and run during a unit test.