You probably know it is a good idea to test your Angular Apps because it pays for itself in the form of easier maintenance and fewer errors. As I explained in my guide to Angular testing, unit tests should be the bedrock of your test coverage as they are the cheapest to maintain. Compared to more sophisticated testing frameworks like XUnit the tools for mocking in an Angular CLI project are not impressive out of the box. Among my favorite mocking frameworks are NSubstitute for C# applications, as it requires very little code to mock out classes, compared to what it takes to create mocks for Angular services and components. The mocking process can be so tedious that some teams get tempted to either “white-box” test classes by making certain methods public for overriding them instead of creating mocks or don’t create tests at all.
Either way, writing tests should be easy and this post is covering how to make testing easy by covering the best ways to write mocks in an Angular app.
Should I create a unit test or an integration test?
You can avoid mocking altogether if you decide to create an integration test but how do you know if you should create a unit test or an integration test?
Create a unit test when:
- The unit under test contains logic that needs to be tested separately to create a useful test, that is, to ensure that a feature is working as expected
- The unit contains complex logic that needs to be tested separately
Create an integration test when:
- When the integration between components is needed for creating a meaningful test
- When testing a component that only contains simple logic that needs interaction with other components to create a useful test
- When testing integrations with third-party libraries
When not to create unit nor integration tests
If the feature under tests relies heavily on interaction with the dom and contains no business logic, are often too fragile and doesn’t give enough value to test to justify investing in covering it with tests. You should do a risk analysis to discover the most critical features in the app to invest automatic end-to-end testing in and, if the feature is of critical value, create end-to-end tests to cover the feature.
Dealing with mocks in an Angular app
Now we are gonna look at how to deal with mocking of dependencies in Angular unit tests. The examples are based on my Angular TODO app with best practices repository.
For mocking component, the easiest solution I have found is to avoid needing to mock components by shallow testing, that is, use
schemas: [NO_ERRORS_SCHEMA] so the component under test’s template is not instantiating component tags. Of course, if you have a test that needs to interact with another component, such as through a view child, you need to have a mock of the component, if you don’t want to create an integration test.
What you will often see in Angular libraries, eg. Angular Routing library, is that it provides a testing module for easier mocking in unit tests. I recommend that you do the same with components you want to create mocks for, by creating a
*component-name*.component.mock.ts beside the component file, so you can easily get a mock of the component. Also, you should make the mock implement the implementation component to ensure that the mock and the component are exposing the same methods.
After having created a mock file for a component, it is easy to get a mock of the component when needed.
An example of a component mock is:
Which is located beside the component file, which gives us a folder structure like this:
The mocks are then imported like this:
I wish there were an easier and less boilerplate way to create mocks for components in Angular, such as creating it with Jasmine spies, but you need to apply the component decorator to the class to make it a component.
Mocking pipes and directives
Mocking pipes and directives is the same principle as with mocking components except when mocking these you can’t do
NO_ERRORS_SCHEMA as the compiler will try to invoke a pipe or directive when evaluating the template, so you need to create a mock for every pipe and directive in your template of the component under test if you don’t want to override the component template in the test or do an integration test. For creating the mock, the same principles applies: create a mock file beside the implementation file with: *pipe/directive-name*.pipe/directive.mock.ts and implement the implementation to ensure that mock and implementation enforces the same contract.
Because services are basically typescript classes these are way easier to mock out. This is one of the reasons that I preach to create component services to encapsulate a component business logic in my refactoring Angular apps post.
The Angular testing library provides us with a method called
createMagicalMock converting all of a service’s method into jasmine spies, making us able to substitute the values and assert that methods have been called.
The service mock methods are in my SpyHelper class:
createMagicalMock method will substitute every method to a spy, making it possible to set a return value and assert that calls have been made to the methods.
Compared to creating mocks for components, this is way easier and all you need to do is to provide a service with the
provideMagicalMock method and then instantiate the mocked services with TestBed.get:
Notice how I’m overriding the todoListServiceMock’s todoList property with:
(todoListServiceMock as any).todoList = todolist;
This is the name of the game, you gotta be a little bad sometimes. I do this so I can set the value of the service ‘s todoList property. Alternatively, I could have made it a public getter, so I could have used
spyOnProperty which could create a spy for the return value.
See how easy this was? Thin down those components by moving the logic to services, so you can write tests with ease.
In this post, we looked at how to mock dependencies in Angular tests. Compared to other test libraries such as XUnit the Angular testing setup is very basic but certain tricks will ease the work of creating mocks in unit tests. We looked at when to use mocks vs. integration tests vs. no tests at all. In the cases where mocks were necessary, we looked at how to create mocks for components, directives, pipes, and services. We saw that services were easiest to mock using the
provideMagicalMock helper method to create a service with all methods substituted with spies.
If you liked this post make sure to comment, follow me on Twitter and subscribe for weekly posts about how to become a better Angular developer.