In the previous two chapters, we started understanding what Angular services are, when to create them, and how to use them. We also started learning how to make HTTP calls and handle the various use cases that crop up when working with servers.
In this chapter, we will take a step back and try to see how we can unit test these services. We will first see how to unit test a service, followed by understanding how to leverage the Angular dependency injection system to mock out service dependencies in unit tests. Finally, we will dig into writing unit tests when we are working with HttpClient.
If you want to quickly recap what unit tests are and how to write them for components, you can refer to Chapter 5.
The first thing we will start with is learning how to unit test very simple services. These might be services without any dependencies that act as encapsulators of business logic or functionality that needs to be reused across our application.
We will start with testing the very simple service we built in Chapter 8. You can use the codebase in chapter8/simple-service as the base for this section. The finished code is available in chapter10/simple-service.
Any time we are unit testing our service, we must do a few things over and above what we did for testing components. Namely:
Configure the Angular TestBed with a provider for the service we want to test.
Inject an instance of the service we want to test either into our test or as a common instance in the beforeEach.
When you generate a service using the Angular CLI, this initial skeleton is generated for you out of the box. Regardless, let’s first take a look at the skeleton spec that was generated in src/app/services/stock.service.spec.ts:
import{TestBed,inject}from'@angular/core/testing';import{StockService}from'./stock.service';describe('StockService',()=>{beforeEach(()=>{TestBed.configureTestingModule({providers:[StockService]});});it('should be created',inject([StockService],(service:StockService)=>{expect(service).toBeTruthy();}));});
The basic skeleton itself allows us to walk through some of the initial setup that we mentioned. Let’s cover the most important bits:
In the beforeEach, like we used to register the components, we now register the provider for the StockService. This ensures that the test is now running in the context of our testing module.
In the it, which is the actual test, instead of just passing the test function to it, we call inject, which is a function provided by the Angular testing utilities. We pass an array to it as the first argument, which are the Angular services that need to be injected into our test. The second argument is a function that gets the arguments in the same order that we passed to the array. We write our actual test within this function.
In the skeleton code, we instantiate the StockService provider with the testing module, and then just make sure that when we inject it in the test, we get an instantiated version of the service for use in the test.
Before we get on to the actual test, let’s quickly review the service that we will be testing. The StockService currently looks like the following:
import{Injectable}from'@angular/core';import{Stock}from'app/model/stock';@Injectable()exportclassStockService{privatestocks:Stock[];constructor(){this.stocks=[newStock('Test Stock Company','TSC',85,80,'NASDAQ'),newStock('Second Stock Company','SSC',10,20,'NSE'),newStock('Last Stock Company','LSC',876,765,'NYSE')];}getStocks():Stock[]{returnthis.stocks;}createStock(stock:Stock){letfoundStock=this.stocks.find(each=>each.code===stock.code);if(foundStock){returnfalse;}this.stocks.push(stock);returntrue;}toggleFavorite(stock:Stock){letfoundStock=this.stocks.find(each=>each.code===stock.code);foundStock.favorite=!foundStock.favorite;}}
The StockService has three stocks that are instantiated by default. When we call getStocks(), this list of three stocks is returned. We can also add stocks by calling createStock, which checks for the presence of the stock and then adds it.
Now let’s improve it to actually test the service, which had the capability of getting a list of stocks and adding stocks. We will add two tests for these two methods:
/** Other imports skipped for brevity **/import{Stock}from'app/model/stock';describe('StockService',()=>{varstockService:StockService;beforeEach(()=>{/** No change in first beforeEach, skipping for brevity **/});beforeEach(inject([StockService],(service:StockService)=>{stockService=service;}));it('should allow adding stocks',()=>{expect(stockService.getStocks().length).toEqual(3);letstock=newStock('Testing A New Company','TTT',850,800,'NASDAQ');expect(stockService.createStock(stock)).toBeTruthy();expect(stockService.getStocks().length).toEqual(4);expect(stockService.getStocks()[3].code).toEqual('TTT')});it('should fetch a list of stocks',()=>{expect(stockService.getStocks().length).toEqual(3);expect(stockService.getStocks()[0].code).toEqual('TSC');expect(stockService.getStocks()[1].code).toEqual('SSC');expect(stockService.getStocks()[2].code).toEqual('LSC');});});

Inject StockService into another beforeEach and save it for access across tests

Ensure that we start with the original three stocks from our service

Add the stock and ensure that it returns true

Check for the presence of the stock we added in the service

Ensure that we start with the original three stocks from our service

Check each stock to ensure it is the data we expect
We have made a slight change to the initialization logic, and added two pretty mundane, run-of-the-mill tests:
Instead of writing an inject block in each test (each it block), we have moved that logic to another beforeEach block, which is solely responsible for setting up local variables that will be repeatedly used across all the tests. Note that this does not mean the same instance is used in all the tests, but that we don’t have to inject the service into each test individually.
We have added two tests, one to test adding of stocks and another to check the default getStocks() call. Again, note that the two tests are independent. Our adding a stock in the first stock does not result in there being four stocks in the second test. Before each test, we are creating a new testing module with a new instance of the service.
Other than this, the test itself is pretty straightforward and self-explanatory. To run this test, simply execute:
ng test
That should automatically start Karma, capture Chrome, run the tests, and report the results right in your terminal window.
What if our service itself had a dependency on another service? Well, we would handle it exactly the same way. We have a few options:
Register the dependency as a service with the Angular TestBed module, and let Angular be responsible for injecting it into the service we are testing.
Override/mock the dependent service by registering a fake/stub provider with the TestBed, and rely on that instead of the original service.
For both of these, we would simply add another provider in the TestBed.configureTestingModule call for the other service. We will see this principle in action in the next section.
Next, let’s see how to deal with two slightly different situations:
If we had to test a component, and actually use the real service underneath in the test.
If we had to test a component, and we wanted to mock out the service it depends on in the test.
First, let’s take a look at the test for StockListComponent if we were using the real service underneath. Our src/app/stock/stock-list/stock-list.component.spec.ts file would look like the following:
import{async,ComponentFixture,TestBed}from'@angular/core/testing';import{StockListComponent}from'./stock-list.component';import{StockService}from'app/services/stock.service';import{StockItemComponent}from'app/stock/stock-item/stock-item.component';import{Stock}from'app/model/stock';describe('StockListComponent With Real Service',()=>{letcomponent:StockListComponent;letfixture:ComponentFixture<StockListComponent>;beforeEach(async(()=>{TestBed.configureTestingModule({declarations:[StockListComponent,StockItemComponent],providers:[StockService]}).compileComponents();}));beforeEach(()=>{fixture=TestBed.createComponent(StockListComponent);component=fixture.componentInstance;fixture.detectChanges();});it('should load stocks from real service on init',()=>{expect(component).toBeTruthy();expect(component.stocks.length).toEqual(3);});});

Add StockItemComponent to the TestBed declarations array

Add StockService to the providers array

Ensure that the stocks in the component are loaded from the service
Most of the test is the autogenerated skeleton from the Angular CLI. The changes we have made in particular are:
We have added a declaration of StockItemComponent in addition to the StockListComponent. This is because the template for our StockListComponent uses the StockItemComponent and hence it is needed for our test to succeed.
We have added StockService in the array passed to the providers section of the testing module created. This ensures that in our test, we use the actual underlying StockService whenever it is asked for by any component.
Finally, we have added an assertion to ensure that we load the three stocks that are returned by our StockService when the component is initialized.
If you notice, this should be similar to how we tested the service itself in the previous section. We simply add a provider for our service and then we have the real live service in our tests accessible.
Next, let’s see how we could write a similar test, but instead of using the real service in the test, how to mock out certain calls instead of creating a brand-new fake service just for our test. This approach is useful when we do actually want to use most of the service, but just override/mock out certain calls. This is also less cumbersome than creating and maintaining a full parallel fake implementation.
Let’s see how we could use the real service with just some calls mocked out in our test. Our revamped test in src/app/stock/stock-list/stock-list.component.spec.ts would look like this:
/** Standard imports, skipping for brevity **/describe('StockListComponent With Mock Service',()=>{letcomponent:StockListComponent;letfixture:ComponentFixture<StockListComponent>;letstockService:StockService;beforeEach(async(()=>{/** No change in TestBed configuration, skipping for brevity **/}));beforeEach(()=>{fixture=TestBed.createComponent(StockListComponent);component=fixture.componentInstance;// Always get the Service from the Injector!stockService=fixture.debugElement.injector.get(StockService);letspy=spyOn(stockService,'getStocks').and.returnValue([newStock('Mock Stock','MS',800,900,'NYSE')]);fixture.detectChanges();});it('should load stocks from mocked service on init',()=>{expect(component).toBeTruthy();expect(component.stocks.length).toEqual(1);expect(component.stocks[0].code).toEqual('MS');});});

Get the StockService instance through the component’s injector

Mock out the getStocks() call and return a hardcoded value instead

Ensure that the stocks are provided from our mocked-out call now
Our test looks mostly similar to the test in the previous section, especially in how we hook up the service as a provider to the TestBed. The major difference is in the second beforeEach, where we use the injector in the test fixture to get a handle on the StockService.
There are two ways for us to get the service instance in our tests. We can either rely on the inject function from the Angular testing utilities to inject the service instance, like we did in our test for StockService, or we can use the injector reference on the element, like we just did here.
Once we have a handle on the service instance, we can use Jasmine spies to spy on different methods on the service. A spy (whether it is from Jasmine or any other framework) allows us to stub any function or method, and track any calls to it along with its arguments and also define our own return values.
In this case, we use spyOn to spy on a particular method in the service (the getStocks() call), and use it to change the return value to what we want, rather than the original underlying call. This way, the actual service call never gets invoked.
Our test then just has assertions to make sure the return value is from our mocked service call rather than the original service.
The last option we have when we are writing tests for components or services that depend on other services is to replace the actual service with a fake that we have created just for the purpose of testing. This allows us to create a service tuned just for the test that gives us maybe more access, or just tracks what APIs are called with what values. Most of this could also be accomplished with Jasmine spies, but if you have a repeated use case, then it might make more sense to create a fake that can be reused.
For us, a fake would simply be an object that we create, which has the same API as the service we are mocking, but our own (hardcoded) implementation of the methods underneath. For example, our fake could simply return a different hardcoded list of stocks instead of making a server call in a test.
Let’s see how we could use a fake service in our test. Our revamped test in src/app/stock/stock-list/stock-list.component.spec.ts would look like this:
/** Skipping imports for brevity **/describe('StockListComponent With Fake Service',()=>{letcomponent:StockListComponent;letfixture:ComponentFixture<StockListComponent>;beforeEach(async(()=>{letstockServiceFake={getStocks:()=>{return[newStock('Fake Stock','FS',800,900,'NYSE')];}};TestBed.configureTestingModule({declarations:[StockListComponent,StockItemComponent],providers:[{provide:StockService,useValue:stockServiceFake}]}).compileComponents();}));beforeEach(()=>{fixture=TestBed.createComponent(StockListComponent);component=fixture.componentInstance;fixture.detectChanges();});it('should load stocks from fake service on init',()=>{expect(component).toBeTruthy();expect(component.stocks.length).toEqual(1);expect(component.stocks[0].code).toEqual('FS');});});

Define a stockServiceFake JavaScript object that implements a getStocks() method

Specify the instance to use when providing the StockService

Ensure that the values are coming from our fake service
There are a lot of differences in the test from the previous sections, so let’s walk through some of the major changes:
We first create a fake service instance, called stockServiceFake. Note that we initialize it with only one of the methods (getStocks()), and not necessarily all the APIs in the service.
When we configure the testing module using the TestBed, instead of registering the StockService, we register a provider. We tell Angular that whenever someone asks for StockService (using the provide key), provide the value stockServiceFake (using the useValue) key. This overrides the default behavior of providing the class instance.
Generally, when we identify a class as a provider to Angular, Angular would be responsible for instantiating an instance of the class and providing it when a component or a service depends on it.
In certain cases, we don’t want Angular to instantiate it, but rather we want to define what value to use. That is when we use the mechanism we used in the preceding code, where we can define what class is being provided (using the provide key) and specify the instance to use, rather than letting Angular create the instance (using the useValue key).
You can read up more on the different ways you can configure Providers in the official Angular docs.
Other than this, our test looks similar. We again assert that the data returned to the component is from our fake service, and not from the original service.
Note that the recommended way to get the handle on an instance of a service, even when using fakes, is through the injector. This is because the fakeStockService instance we create in our test will not be the same as the instance provided by the Angular dependency injector. Thus, even with a fake, if you want to assert, for example, that a stock was added successfully to the service, you would want to do it against the service returned by the injector, and not the original instance we used.
You can either let Angular’s dependency injection provide it for you using the inject method from the Angular testing utilities or use the injector instance on the element created.
Angular’s dependency injector creates a clone of the stub/fake you provide to it and injects that instead of the original instance.
You can check this out by tring to write the test against the service instance in the test versus the way we have written it here, and see the behavior of the test. You will see that the test fails, because the original instance does not change.
All three of these tests are available in chapter10/simple-service/src/app/stock/stock-list/stock-list.component.spec.ts, one after the other as three describe blocks.
So far, we have seen how we might test simple and straightforward services, with or without further dependencies, as well as test components. But the services we have worked with so far have been synchronous. In this section, we will dig a little bit into how to handle and write tests when the underlying service or code deals with asynchronous flows.
With any code that touches an asynchronous flow, we have to be careful in our tests and recognize at which point the hand-off happens from synchronous flow to asynchronous, and deal with it accordingly. Thankfully, Angular provides enough helpers to abstract out some of the complexities of this in our test.
Fundamentally, we have to make sure our test itself is identified and running as an asynchronous test. Secondly, we must ensure that we identify when we have to wait for the asynchronous part to finish, and validate the changes after that.
Let’s see how a test for the CreateStockComponent might look, when we had just switched to using observables (but not HTTP yet). We will use the codebase from chapter8/observables as the base on which to write our tests.
We will add (or modify if the skeleton already exists) the src/app/stock/create-stock/create-stock.component.spec.ts file, and change it as follows:
import{async,ComponentFixture,TestBed}from'@angular/core/testing';import{CreateStockComponent}from'./create-stock.component';import{StockService}from'app/services/stock.service';import{Stock}from'app/model/stock';import{FormsModule}from'@angular/forms';import{By}from'@angular/platform-browser';describe('CreateStockComponent',()=>{letcomponent:CreateStockComponent;letfixture:ComponentFixture<CreateStockComponent>;beforeEach(async(()=>{TestBed.configureTestingModule({declarations:[CreateStockComponent],providers:[StockService],imports:[FormsModule]}).compileComponents();}));beforeEach(()=>{fixture=TestBed.createComponent(CreateStockComponent);component=fixture.componentInstance;fixture.detectChanges();});it('should create stock through service',async(()=>{expect(component).toBeTruthy();component.stock=newStock('My New Test Stock','MNTS',100,120,'NYSE');component.createStock({valid:true});fixture.whenStable().then(()=>{fixture.detectChanges();expect(component.message).toEqual('Stock with code MNTS successfully created');constmessageEl=fixture.debugElement.query(By.css('.message')).nativeElement;expect(messageEl.textContent).toBe('Stock with code MNTS successfully created');});}));});

The CreateStockComponent needs FormsModule to work

Pass the return value of calling async as the second param to the it function

Wait for the test fixture to finish executing asynchronous flows

Update the view after the changes
The early part of the unit test remains pretty much the same, from initializing the TestBed with the module to the beforeEach. We also have to import the FormsModule for the CreateStockComponent to work.
The first difference comes in the it declaration itself of our asynchronous test. Instead of simply passing the function containing our test code to the it block, we now pass an async function, which in turn is passed the function containing our test code. Do not forget this part when writing an asynchronous unit test.
The second major difference is after calling the function under test, createStock(), which actually triggers the asynchronous flow. Normally, we would write our assertions right after this. In the case of an asynchronous flow, we need to ask Angular’s test fixture to stabilize (that is, wait for the asynchronous parts to finish). The whenStabilize returns a promise that on completion allows us to run the remaining part of the test. In this case, we tell Angular to detect any changes and update the UI, and then make our assertions.
In this particular case, even if we had skipped the whenStabilize bit and directly written our assertions, our test might still have passed. But that is only because our actual underlying service is also synchronous, even though it does return an observable. If it was truly asynchronous, then the whenStable becomes critical for us. Thus, it is generally a good practice to aways use it when it comes to async tests.
The finished code for this example is available in the chapter10/observables folder in the GitHub repository.
The last thing we will take a look at in this chapter is to see how to test HTTP communication. In particular, we will dig into how to mock out server calls. We will see how to leverage Angular’s built-in testing utilities that it provides to test HTTP communication.
For this section, we will use the code from chapter9/simple-http as the base, and see how we can test both GET calls like fetching a list of stocks as well as POST calls that we make to create stocks.
First, let’s test the StockListComponent to see how we can test the initialization logic of fetching the list of stocks from our server. We want to ensure the entire flow of making a server call, getting the list of stocks, and then displaying it.
We will modify src/app/stock/stock-list/stock-list.component.spec.ts as follows:
/** Standard imports, skipping for brevity **/import{HttpClientModule}from'@angular/common/http';import{HttpClientTestingModule,HttpTestingController}from'@angular/common/http/testing';import{By}from'@angular/platform-browser';describe('StockListComponent With Real Service',()=>{letcomponent:StockListComponent;letfixture:ComponentFixture<StockListComponent>;lethttpBackend:HttpTestingController;beforeEach(async(()=>{TestBed.configureTestingModule({declarations:[StockListComponent,StockItemComponent],providers:[StockService],imports:[HttpClientModule,HttpClientTestingModule]}).compileComponents();}));beforeEach(inject([HttpTestingController],(backend:HttpTestingController)=>{httpBackend=backend;fixture=TestBed.createComponent(StockListComponent);component=fixture.componentInstance;fixture.detectChanges();httpBackend.expectOne({url:'/api/stock',method:'GET'},'Get list of stocks').flush([{name:'Test Stock 1',code:'TS1',price:80,previousPrice:90,exchange:'NYSE'},{name:'Test Stock 2',code:'TS2',price:800,previousPrice:900,exchange:'NYSE'}]);}));it('should load stocks from real service on init',async(()=>{expect(component).toBeTruthy();expect(component.stocks$).toBeTruthy();fixture.whenStable().then(()=>{fixture.detectChanges();conststockItems=fixture.debugElement.queryAll(By.css('app-stock-item'));expect(stockItems.length).toEqual(2);});}));afterEach(()=>{httpBackend.verify();});});

Include the HttpTestingController as a local variable

Import the HttpClientModule and the HttpTestingController in the module

Set expectation that one call to /api/stock will be made as part of the test

Define a list of hardcoded stocks to return when the GET call is made

Wait for the Angular task queue to get empty, and then proceed

Verify that the GET call to /api/stock was actually made as part of the test
While the test seems very long, the changes to support testing HTTP calls are actually pretty straightforward. The major change when it comes to writing tests for code that makes XHR calls is the use of the HttpTestingController. In our unit test, while we want to test the code flow that makes XHR calls, we don’t really want to make the actual underlying call. Any network call in a test adds unreliable dependencies, and makes our tests brittle and possibly nondeterministic.
For this reason, we actually mock out the XHR calls in our tests, just check that the right XHR calls are made, and that if the response was a certain value, then it is handled correctly. Therefore, a lot of our tests would simply be setting up through the HttpTestingController what calls to expect and what responses to send when those calls are made. You even have control over whether the server responds with a 200 success response, or whether it is an error (a 400 or 500 response, which corresponds to a client- or server-side error, respectively).
With that context, let’s walk through the points of interest in the preceding test:
The first and most important change is that we need to import the HttpClientModule so that our service can initialize with the injected HttpClient.
The second thing is that we include the HttpClientTestingModule from @angular/common/http/testing. This testing module helps automatically mock out the actual server calls, and replaces it with a HttpTestingController that can intercept and mock all server calls.
We hook these two modules up in the imports section of the TestBed module configuration.
Then in our test (or in this case, the beforeEach), we can inject an instance of the HttpTestingController to set our expectations and verify server calls.
After initializing the component instance as usual, we then start setting our expectations on the HttpTestingController. In this case, we expect that one GET call to the URL /api/stock. We also pass it a human-readable text to print in the logs in case the test fails or that call is not made.
In addition to setting expectations on calls made, we can also define the response Angular should send when those calls are made. Here, we return an array of two stocks by calling the flush method. The first argument to flush is the response body.
The rest of the test is straightforward. We expect the component to be initialized. Then, we wait for the change detection to stabilize (by calling fixture.whenStable()), and then ensure that the response we returned is actually rendered in the template correctly.
In the afterEach, we call httpBackend.verify(). This ensures that all the expectations we set on the HttpTestingController were actually satisfied during the run of the test. It is generally good practice to do this in the afterEach, to ensure that our code doesn’t make extra or fewer calls.
Let’s quickly write one more test to see how to handle POST calls, and how to send non-200 responses from the server. We will create the src/app/stock/create-stock/create-stock.component.spec.ts file as follows:
/** Standard imports, skipping for brevity **/describe('CreateStockComponent With Real Service',()=>{letcomponent:CreateStockComponent;letfixture:ComponentFixture<CreateStockComponent>;lethttpBackend:HttpTestingController;beforeEach(async(()=>{/** TestBed configuration similar to before, skipping for brevity **/}));beforeEach(inject([HttpTestingController],(backend:HttpTestingController)=>{httpBackend=backend;fixture=TestBed.createComponent(CreateStockComponent);component=fixture.componentInstance;fixture.detectChanges();}));it('should make call to create stock and handle failure',async(()=>{expect(component).toBeTruthy();fixture.detectChanges();component.stock={name:'Test Stock',price:200,previousPrice:500,code:'TSS',exchange:'NYSE',favorite:false};component.createStock({valid:true});lethttpReq=httpBackend.expectOne({url:'/api/stock',method:'POST'},'Create Stock with Failure');expect(httpReq.request.body).toEqual(component.stock);httpReq.flush({msg:'Stock already exists.'},{status:400,statusText:'Failed!!'});fixture.whenStable().then(()=>{fixture.detectChanges();constmessageEl=fixture.debugElement.query(By.css('.message')).nativeElement;expect(messageEl.textContent).toEqual('Stock already exists.');});}));afterEach(()=>{httpBackend.verify();});});

Expect a POST request to be made to /api/stock during the test

Ensure that the body of the POST request is the same as the stock we created in the component

Define the response for the POST request, which is a failure 400 response

Check that the server response is shown correctly by the component

Ensure that the POST request happened during the test
Most of the preceding test should be very similar to the test we wrote for the StockListComponent. Let’s talk about the major differences in detail:
Instead of immediately flushing the response when we set the expectation on the httpBackend for the POST call, we save it to a local variable.
We can then write expectations on the various parts of the HTTP request, like the method, URL, body, headers, and so on.
We then ensure that the body of the request matches the stock in our component.
Finally, we flush a response, but instead of just flushing the body, we pass a second options argument to configure the response further. We mark the response as a 400 response to trigger the error condition.
The rest of the test doesn’t change, from waiting for the fixture to stabilize, asserting on the elements, to verifying that the calls were made on the httpBackend.
httpBackend.expectOne also takes an HttpRequest object instead of passing the URL and the method as a config object. In such a case, we can actually configure the HttpRequest object with the body of the POST request as well. Note that this does not enforce that the POST request is made with that body. You will still need to manually check it the way we did in the previous example. Do not assume that the body is automatically matched and forget to verify it.
The finished code is available in the chapter10/simple-http folder of the GitHub repository.
In this chapter, we dug deeper into testing services in Angular. We looked at how we could test services, as well as components that use services. We explored the various techniques and options to deal with services, from using the real service, to mocking it or stubbing it out. Then we moved onto seeing how we could handle async behavior when it came to unit tests, and then finally how to deal with XHR and HTTP in our tests.
In the next chapter, we will switch back to seeing how we can enhance our Angular applications with the ability to deep-link to certain pages and components. We will see how to start setting up our routes, as well as protect certain routes to be accessible under only certain conditions.
Take the finished exercise from Chapter 9 (available in chapter9/exercise/ecommerce). Given this, now try to accomplish the following:
Update the ProductListComponent tests. Remove the isolated unit tests. Update the Angular tests to use the HttpTestingController to provide the list of stocks as well as handle quantity change.
Ensure that the list of stocks is reloaded when the quantity changes.
Add tests for the positive and negative cases of the CreateProductComponent. Check that the event is emitted when a product is successfully created as well.
Most of this can be accomplished using concepts covered in this chapter. You can check out the finished solution in chapter10/exercise/ecommerce.