Angular Unit Testing
Usual developer story. The developer works on a task and makes changes related to the feature. Post that, they test affected use cases manually. Then commit, QA, and deploy. But this process may not work 100% of the time.
The manual test can introduce error-prone situations. This may easily break the app. We can guard such situations against happening by introducing another testing layer. That can be Unit testing, e2e testing, integration testing, API testing, etc.
What is Unit Testing?
As the name suggests, Unit means a small piece of code/function that would be tested against both positive and negative use cases.
- It helps to test units/functions/methods.
- Unit tests are specifications of a particular unit
- In the long run, unit testing can be recognized as business requirement details
- A next developer coming after you can get the gist of the code by looking at unit tests.
- Unit tests focus should be to test small units and mock/spy everything else.
- NodeJS : Should install NodeJS on your machine
- Angular CLI : CLI install
- New project created using Angular CLI.
- Jasmine : Understanding about Jasmine test framework.
What is Angular Unit Testing?
Unit testing in Angular is the process of testing isolated units of code blocks. Angular unit testing aims to uncover the hidden issues, unhandled edge cases, and unexpected behavior of specific code blocks. These issues can be easily overlooked by testers or developers. Enterprise great projects have hundreds of thousands of developers working on different verticals that may have very less understanding of what they are working on. In such cases, looking at unit tests (a.k.a. specifications) can give a better idea of what exactly it suppose to do. Adding negative unit tests is as important as adding positive test cases.
We can use various tools to perform testing in the angular app :
- Jasmine Karma
- Testing Library
Why Should You Unit Test Angular Apps?
For the developer, it is really hard to test all use cases from the code blocks perspective every time after developer makes changes. And it is easy to miss a few scenarios that will lead to a bug. Eventually, what happens sometime is the developer fixes one bug, and that opens another couple of bugs somewhere else. This seems to be natural as that developer might not know the side effect of what had changed. This complete situation can easily be managed by unit testing in angular applications. By introducing unit testing, there will be very few chances of the system getting error-prone.
Over time, you may call your unit tests to feature specifications. In the long run, the new developer coming into a project can get benefit because of existing unit tests. By looking at them, they may understand the expected behaviors. If in case there is a bug or new feature introduction. The new developer had to modify or amend the tests.
Unit Testing with Jasmine and Karma for Angular Apps
Angular CLI newly created project comes with default jasmine tests configuration. You just need to execute the ng test command to see it running.
Jasmine's testing framework provides a platform for writing a test suite. Karma is a task runner which assists in running tests. It runs tests based on configuration (karma.config.js generated by angular CLI). Karma
- Provide an option to debug tests on chrome.
- See tests up on a browser.
- Can generate code coverage reports.
Another important file is test.ts. It is an entry point for running all spec.ts files.
- line:16-19 is mainly responsible for initializing the testing environment.
- line:22 gathers all files ending with spec.ts.
- line:24 executes them.
- Above all is provided by default, no need to make any changes here.
How to Create an Angular Test App?
Create a New Angular Application
Execute an angular CLI command in the terminal. It would contain karma.config.js and src/test.ts(entry file for tests).
Folder structure :
Running the ng test command would trigger test.ts that has been configured inside the angular.json file.
From angular.json, look at the architect > test configuration. On line:27 src/test.ts. And we have already seen how test.ts gather and executes all file names ending with .spec.ts.
How to Write a Unit Test in Angular?
There is a specific way to write test suites in Jasmine. Here are a few important functions to use,
- beforeEach :
initialization logic for all specs (depends on where it has been written).
- describe :
It can be considered as a block of multiple specifications. describe can have multiple describe's or it's under it.
- it :
Represent a single specification. it can not nest describe or another it inside.
- expect :
expect is used for assertions.
- afterEach :
tear down logic for all specs (depends on where it has been written).
Another important principle that you suppose to follow is AAA. AAA stands for
- Arrange : Make arrangements before performing tests, for, e.g. mocking, spying, and stubbing.
- Act : Call the unit block
- Assert : Expect desired value or call sequence to be called.
Create a Sample Math Operation Application
Let's create a simple Math Operation application. For now, add a simple addition operation.
- Two input box accepts a number
- Number should be greater than 0
- Till then "Calculate" button will be disabled
- Enabled, if numbers are valid
- On click of the "Calculate" button, it should add two numbers.
How to Test an Angular Service?
We have seen what functionality is expected from the app. Unit testing is test units of our application. Here math.service.ts is involved in the calculation. Let's test that.
For a while, comment app.component.spec.ts file below the code. And then, run the ng test command.
When you generated math service using Angular CLI, it must have created a spec file along with it looks like below.
Let's handle the below use cases.
- It should return 0 if the first number is NaN
- It should return 0 if the second number is NaN
- It should return the addition first and the second number is valid
Understanding of Service tests
- line:5-6 is a configuration of TestBed with service that has been injected.
- This configuration of TestBed is done inside beforeEach
- Then followed by each it block with various use cases.
How to Test an Angular Component?
To test components, we have to follow the same thing. Primarily configure TestBed and then follow with it blocks.
Make sure you follow Arrange Act Assert principle.
Use cases to cover
- Should handle validation.
- Calculate button should be disabled when fields are invalid.
- Result should be displayed after calculate button is clicked.
Now understand the specs written above step by step.
Initialization of Test
Refer to the below code, a subset of the original spec file. It is responsible for the TestBed configuration
- line:1 beforeEach will trigger before each test unit block
- line:2-7 configuring Testing module over TestBed
- line:3-5 define a declarations of component
- line:8 imports FormsModule as we've used it on the template.
- line:9 compiles all components so that they can be used.
Basic Component Init Test
line:12-17 component instantiation test
- line:1 it block contains the specification
- line:2 createComponent on TesBed using the AppComponent class. This step will be followed in all tests.
- line:5 assertion of result.
So far, we have seen very simple tests. The next tests are kind of a bit to complete handling original use cases. They would be following the AAA principles, as we discussed before.
We will begin understanding the specs written for validations.
- line:3 Create a component on TestBed
- line:4-5 get hold of both component instance and dom element
- line:8 set first number value to -1 (which is invalid)
- line:9 fire the detectChanges method to update bindings
- line:12 call the fixture.whenStable method until it resolves.
- line: 13 Once resolved, check button disabled state, assertion to check it should be true
We won't be going over every step. But let's have one more unit block. And decipher it step by step. We can go for the last spec.
- Arrange :
We create an element and get hold of its instance and DOM element.
- Act :
Added number 4 in both input boxes, and click on calculate button. Followed by detectChanges to update binding on the page.
- Assert :
It should show the expected result in the b tag.
How to Test an Async Operation in Angular?
Suppose we amend our MathService to store the results on the server. This would be an async method. Currently, it is making a fake call. But originally, this would return a promise. And we would need to wait until it resolves it. And the received result will be returned back.
To write tests to handle async, define the it block arrow function as async and use await on the asynchronous method, as the traditional way to approach this problem in the JS world.
- We have imported HttpClientTestingModule inside the TestBed configuration.
- Keep the HTTP instance in the local variable.
- Arrange block is quite interesting here.
- defined our expected result
- Using the spyOn method, return a fake value on the http.get method call.
- Act block call save result async method, with await before it.
- Assert check for expected result.
We emphasize more on the Arrange Act Assert principle because that's the basis of the unit testing mental model. Once you master the AAA principle, you can write tests on any platform.
How to Write a Negative Unit Test in Angular?
Think about negative scenarios and what scenarios the system can break. Put them on the unit level to handle these edge cases. This way, we can make sure the code is producing less error-prone. It doesn't mean that you handled all negative use cases, and there won't be any bugs in the system. Of course, not. Software development is progressive. We can have a business-side bug. Though, when we fix those bugs, make sure we add unit tests to cover them so that the bug won't discover again. In short negative unit testing can lead to good software quality. That's why you can see that math.service.spec.ts and app.component.spec.ts have handled various negative scenarios.
We have learned :
- What is the importance of unit testing in angular
- What are the different tools exist in the market for testing
- how to perform unit testing in angular using Jasmine and Karma
- How to approach writing unit test, AAA principle.
- how to test Components and Services in Angular.
- How asynchronous behavior can be tested in Angular.