Everyday we are seeing a bigger push towards adding automated tests to our apps. Whether these are unit tests, integration or e2e tests.
This will be a series of articles based on writing unit tests for Angular and some of it’s core concepts: Components, Services, Pipes and Guards.
These articles are not intended to be comprehensive, rather a soft introduction to unit testing. For more detailed component testing documentation, Angular has a great docs page here: https://angular.io/guide/testing
It’s worth noting that some of my opinionated approaches to testing will come through in this article. Testing is a very opinated topic already. My advice to look through all the testing strategies that are out there and make decide what you think is the best approach.
In this article, we will explore testing components, ranging from simple to more complex components and we will cover the following:
- What is a unit test? 💡
- Why write unit tests? 🤔
- Ok, now how do we write unit tests? 😄
We will be using the standard Jasmine and Karma testing setup that Angular provides out of the box on apps generated with the Angular CLI.
💡 What is a unit test?
A unit test is a type of software testing that verifies the correctness of an isolated section (unit) of code.
Lets say you have a simple addition function:
This full function can be considered a unit, and therefore your test would verify that this unit is correct. A quick test for this unit could be:
We’re introducting a few concepts here.
it(...args) is the function that will set up our unit test. It's pretty common testing terminology across Test Runners.
We also introduce the AAA Test Pattern. It’s a pattern that breaks your test into 3 sections.
The first section is Arrange: Here you perform any set up required for your test.
The second section is Act: Here you will get your code to perform the action that you are looking to test.
The third and final sction is Assert: Here you will make verify that the unit performed as expected.
In our test above we set what we are expecting the value to be if the function performs correctly and we are setting the data we will use to test the function.
We then call the
sum() function on our previously arranged test data and store the result in a
Finally, we check that the
total is the same as the value we are expecting.
If it is, the test will pass, thanks to us using the
.toBe() is a matcher function. A matcher function performs a check that the value passed into the
expect() function matches the desired outcome. Jasmine comes with a lot of matcher functions which can be viewed here: Jasmine Matchers
🤔 But Why?
Easy! Confidence in changes.
As a developer, you are consistently making changes to your codebase. But without tests, how do you know you haven’t made a change that has broken functionality in a different area within your app?
You can try to manually test every possible area and scenario in your application. But that eats into your development time and ultimately your productivity.
It’s much more efficient if you can simply run a command that checks all areas of your app for you to make sure everything is still functioning as expected. Right?
That’s exactly what automated unit testing aims to achieve, and although you spend a little bit more time developing features or fixing bugs when you’re also writing tests, you will gain that time back in the future if you ever have to change functionality, or refactor your code.
Another bonus is that any developer coming along behind you can use the test suites you write as documentation for the code you write. If they don’t understand how to use a class or a method in the code, the tests will show them how!
It should be noted, these benefits come from well written tests. We’ll explore the difference between a good and bad test later.
😄 Let’s write an Angular Component Test
We’ll break this down into a series of steps that will cover the following testing scenarios:
- A simple component with only inputs and outputs
- A complex component with DI Providers
Let’s start with a simple component that only has inputs and outputs. A purely presentational component.
🖼️ Presentational Component Testing
We’ll start with a pretty straight forward component
user-speak.component.ts that has one input and one output. It'll display the user's name and have two buttons to allow the user to talk back:
If you used the Angular CLI (highly recommended!) to generate your component you will get a test file out of the box. If not, create one
.spec.ts is important. This is how the test runner knows how to find your tests!
Then inside, make sure it looks like this initially:
Let’s explain a little of what is going on here.
describe('UserSpeakComponent', () => ...) call is setting up a Test Suite for our User Speak Component. It will contain all the tests we wish to perform for our Component.
beforeEach() calls specify code that should be executed before every test runs. With Angular, we have to tell the compile how to interpret and compile our component correctly. That's where the
TestBed.configureTestingModule comes in. We will not go into too much detail on that for this particular component test, however, later in the article we will describe how to change it to work when we have DI Providers in our component.
For more info on this, check out the Angular Testing Docs
it() call creates a new test for the test runner to perform.
In our example above we currently only have one test. This test is checking that our component is created successfully. It's almost like a sanity check to ensure we've set up
TestBed correctly for our Component.
Now, we know our Component class has a
constructor and two methods,
sayGoodbye. As the constructor is empty, we do not need to test this. However, the other two methods do contain logic.
We can consider each of these methods to be units that need to be tested. Therefore we will write two unit tests for them.
It should be kept in mind that when we do write our unit tests, we want them to be isolated. Essentially this means that it should be completely self contained. If we look closely at our methods, you can see they are calling the
emit method on the
speak EventEmitter in our Component.
Our unit tests are not interested in whether the
emit functionality is working correctly, rather, we just want to make sure that our methods call the
emit method appropriately:
Here we meet the
spyOn function which allows us to mock out the actual implementation of the
emit call, and create a Jasmine Spy which we can then use to check if the
emit call was made and what arguments were passed to it, thus allowing us to check in isolation that our unit performs correctly.
If we run
ng test from the command line, we will see that the tests pass correctly. Wonderful.
Hold up! Having two methods that essentially do the same thing is duplicating a lot of code. Let’s refactor our code to make it a bit more DRY:
Awesome, that’s much nicer. Let’s run the tests again:
Uh Oh! 😱
Tests are failing!
Our unit tests were able to catch correctly that we changed functionality, and potentially broke some previously working functionality. 💪
Let’s update our tests to make sure they continue to work for our new logic:
We’ve removed the two previous tests and updated it with a new test. This test ensures that any string that is passed to the
saySomething method will get passed on to the
emit call, allowing us to test both the Say Hello button and the Say Goodbye.
Note: There is an argument around testing JSDOM in unit tests. I’m against this approach personally, as I feel it is more of an integration test than a unit test and should be kept separate from your unit test suites.
Let’s move on:
🤯 Complex Component Testing
Now we have seen how to test a purely presentational component, let’s take a look at testing a Component that has a DI Provider injected into it.
There are a few approaches to this, so I’ll show the approach I tend to take.
Let’s create a
UserComponent that has a
UserService injected into it:
Fairly straightforward except we have injected the
UserService Injectable into our Component.
Again, let’s set up our intial test file
If we were to run
ng test now, it would fail as we are missing the Provider for the
TestBed cannot inject it correctly to create the component successfully.
So we have to edit the
TestBed set up to allow us to create the component correctly. Bear in mind, we are writing unit tests and therefore only want to run these tests in isolation and do not care if the
UserService methods are working correctly.
TestBed also doesn't understand the
app-user-speak component in our HTML. This is because we haven't added it to our declarations module. However, time for a bit of controversy. My view on this is that our tests do not need to know the make up of this component, rather we are only testing the TypeScript within our Component, and not the HTML, therefore we will use a technique called Shallow Rendering, which will tell the Angular Compiler to ignore the issues within the HTML.
To do this we have to edit our
TestBed.configureTestingModule to look like this:
That will fix our
app-user-speak not declared issue. But we still have to fix our missing provider for
UserService error. We are going to employ a technique in Unit Testing known as Mocking, to create a Mock Object, that will be injected to the component instead of the Real UserService.
There are a number of ways of creating Mock / Spy Objects. Jasmine has a few built in options you can read about here.
We are going to take a slightly different approach:
The part we are interested in now is our
providers array. Here we are telling the compiler to provide the value defined here as the UserService. We set up a new object and define the method we want to mock out, in this case
getUser and we will tell it a specific object to return, rather than allowing the real UserSerivce to do logic to fetch the user from the DB or something similar.
My thoughts on this are that every Public API you interact with should have be tested and therefore your unit test doesn’t need to ensure that API is working correctly, however, you want to make sure your code is working correctly with what is returned from the API.
Now let’s write our test to check that we are fetching the user in our
Here we simply create a spy to ensure that the
getUser call is made in the
ngOnInit methoid. Perfect.
We also leverage the
.and.returnValue() syntax to tell Jasmine what it should return to the
ngOnInit() method when that API is called. This can allow us to check for edge cases and error cases by forcing the return of an error or an incomplete object.
Let’s modify our
ngOnInit() method to the following, to allow it to handle errors:
Now let’s write a new test telling Jasmine to throw an error, allowing us to check if our code handles the error case correctly:
Perfect! 🔥🔥 We are now also able to ensure our code is going to handle the Error case properly!
This is a short brief non-comprehensive introduction into Unit Testing Components with Angular with Jasmine and Karma. I will be publishing more articles on Unit Testing Angular which will cover testing Services, Data Services, Pipes and Guards.
If you have any questions, feel free to ask below or reach out to me on Twitter: @FerryColum.
Originally published at https://dev.to on February 15, 2020.