The purpose of this article is to help in writing Angular unit tests. Let it be the fascinating process rather than pain.
Unit tests help to make sure that individual units of application work correctly. It partly protects from code breakdowns and clarifies how the programme will work in various situations. Eventually, it allows to look at your code from the other side and see its weaknesses. There is even an opinion that hard to test code is the candidate for rewriting.
Isolated or Test Bed tests?
Angular unit tests are divided into two types:
- Isolated — independent from Angular. It is easy for writing, reading, maintaining because all dependencies are excluded.
- Angular Test Bed — tests are written via TestBed utility. It allows to set options and initializes testing environment. The utility contains methods, which make a testing process easy. For example, we can check that component was created, how it interacts with a template, other components and dependencies.
In an isolated case, we test code as a usual class. At first, create the class instance, then check how it works in different situations. Before I show some example be aware that I write tests with jest because it runs faster. However, if you prefer karma + jasmine these examples will ok for you too, because there are little differences in the syntax.
Let’s look at modal service example. It has only two methods emitting some value for popupDialog variable. And it doesn’t have dependencies.
You have to remember about an order of code execution. For example, actions, which should be run before each test go to beforeEach block. So, below we need to create the service instance in each expect block:
Angular Test Bed Tests
Now it is time to explore all power of Test Bed utility. Look on the example of elementary component:
The test file seems to be longer, so look on it in parts: At first we set Test Bed configuration:
compileComponents — method making external style and template files inline. This process is async because Angular compiler must get data from a file system.
|If you run tests with the CLI ng test command, you don’t need compileComponents() method, because the CLI compiles the application before running the tests. Accordingly, if you don’t use external template and style files, you don’t have to worry about compileComponents().|
It is necessary for a test to compile components before the method createComponent() is called. Therefore we put the first BeforeEach body in the async method. So its contents will be run in the special async environment. And until the first BeforeEach method is executed, the following BeforeEach does not start.
Thanks to the rendering of all common data in beforeEach, the further code turns out to be much cleaner. First, let’s check that the component is created and has the property:
Next, we want to check that the variable title is inserted into the DOM. Doing so, we expect that it is set to ‘app’. And one important remark — this assignment occurs when the component is initialized. The detectChanges() method triggers CD cycle, so the component is initialized. Before this call, the DOM and the component data connection don’t occur, and therefore the tests don’t pass.
A component with a dependency
Let’s complicate our component by injecting the service:
It seems not to be particularly complicated, but the tests will not pass. Even if you remember to add the service to the providers of AppModule. Because these changes also need to be reflected in TestBed:
We can specify the service itself, but it’s usually better to replace it with a class or object that describes exactly what we need for the tests.
Just imagine the service with many dependencies. And you’ll have to register everything for testing. Not to mention the fact that in this case, we are testing the component. Also testing one thing — is exactly about unit tests.
So, we write down the stub as follows:
As you can see we have set only necessary methods.
|If you want to use for description class|
Next, we add providers in TestBed configuration:
Don’t confuse PopupService with PopupServiceStab. They are different objects — the first is a clone of the second. Great… But we have injected service for some reason:
Now we have to make sure, that method indeed is called. Since in this case the service is specified in the providers of the root module, we can do this:
If it were a service registered in a component providers, we would have to get it like this:
Finally the test itself:
- Set the spy on method open of popup object
- Run CD cycle, during which ngOnInit will be executed with the method being tested
- We are convinced that it was called
Notice that we check exactly service method invoking, but not what it returns or something service-specific. It is better to test this in service
if you don’t want to go crazy.
Service with Http
Recently(Angular 4) test files with requests could look like something truly awful.
Just remember how it was:
However, now you can find many such examples in the Internet. Meanwhile, the developers of Angular haven`t sat idly by, and now we can write tests much easier. Just using HttpClientTestingModule and HttpTestingController.
Let’s see the service:
At first, describe all our protagonists:
What is interesting there? Look at statisticsServiceStub — we stubbing dependencies by analogy with the component. Because it’s time to test only current service. As you can see I just described only necessary methods for testing. Let’s imagine StatisticsService has a lot of methods and dependencies, but we use only one.
Further, declare fake data for server response:
We need import HttpClientTestingModule in TestBed and add all services to providers:
Next step — getting all service instances:
And verify in BeforeEach() that there are no outstanding requests:
And we are approaching the test itself. The simplest thing we can check — service creating:
Then it’s more interesting — we verify that the expected request will receive certain data, which we flush:
Good idea — verify how ReplaySubject works, that is, will subscribers get games?
Finally, the last example checks that the statisticsService send method will be called:
How to facilitate testing?
- Choose the most suitable type of test for a particular case. And just don’t forget about the essence of unit tests.
- Be sure to know all the features of your IDE, which can help you with testing.
- Angular-cli generator creates test files automatically.
- If you have a lot of dependencies in component (directives, child components) you can disable verification of their definition. For this in the TestBed configuration we assign NO_ERRORS_SCHEMA:
It is really difficult to cover in one article all testing nuances. But I think the main is — understanding which tools you have and what you can do with it.
And then fearlessly face difficulties in practice both trivial and complicated cases.
If after this article you begin to understand a little better how to work with tests – hooray!
Be free to write additions and remarks. Sometimes comments can be more useful than an article.
And you can look on the examples there.