Unit testing in Angular


Unit testing is an essential part of software development. As we keep adding new features, we might need to do code changes to the existing code. It is crucial to make sure that any recent code change doesn’t break existing functionality. It can be a very expensive task to check if the existing functionality is working as expected with the new changes. Furthermore, developers doing new changes might not be able to check all scenarios of the existing code. Having unit tests makes it easy to verify that any recent code changes don’t break any existing code.
API Unit testing
When it comes to API unit testing scope is generally pretty clear. We can write test cases at 2 different levels.
Test cases for all functions written in the code
Test cases for all API endpoints exposed.
For each API/ Function, we can write multiple test cases covering different combinations of parameters/ inputs to the functions/APIs. We can also write some test flows by chaining multiple API calls. I.e. Output from one API is passed to another API to complete a flow.
UI Unit Testing
When it comes to UI unit testing, things start becoming a bit confusing. People often prefer using UI automation tools like Cypress or Selenium to test UI rather than writing unit test cases. Automation definitely helps to test end-to-end UI flows. But it is not an alternative for UI unit test cases. Automation is done after the UI flow is completed and most of the time it is done by a different team (QA automation team)
Angular unit testing helps in
Making sure that particular piece of code works as expected.
Making changes / Refactoring existing code.
Finding errors early, during development itself, and hence avoids back and forth between QA and development teams which saves significant time.
Replacing old/deprecated library.
Test case types
Angular application testing is mainly categorized into Unit testing, Integration testing, and Functional testing (End to end Testing). In Angular Unit can be a component, service, pipe, directive, etc.
A unit test can be categorized further into
- Isolated tests: Here we test component functions only.
2. Shallow unit tests: Here we test the component’s template without rendering its children.
Integration testing covers a component, its children, and also a service that the component interacts with to get data from API or some other external source.
Functional or End-to-end testing covers users interacting with Angular applications. This includes checking if some link/ button is visible on a page, reading text, clicking on a link or button, filling and submitting a form. These tests make expectations about what the user sees and reads in the browser. This is pretty much what is covered in regression testing in test automation.
Unit Testing Framework: Angular CLI provides Jasmine as a default testing framework and Karma as a test runner tool if you create your angular application using Angular CLI then everything is configured for you to start writing unit test cases.
If we think about Angular in particular, we can write test cases that actually cover complete flow. But as a developer, it is important to know how much testing is sufficient. We should limit writing test cases till Integration testing. End-to-end flow testing can be done by using specialized automation tools like Cypress or Selenium.
Setting boundaries for test cases
When we are writing test cases for a component we should limit our test cases to one level below and one level above the component. Suppose we are writing a test case for a “ProductList” component, then a component one level below is the one that provides data to this “ProductList” which can be a service called “ProductSerivce”. A component above “ProductList” can be a “ProductDetails” component which takes product_id as input from the “ProductList” component and then displays product information. In case “ProductSerivce” has dependency on some other service say “LogService” in that case when writing test cases for the “ProductList” component we should limit testing to “ProductSerivce” only. Methods of “LogService” called in “ProductSerivce” should be mocked. Similarly, if “ProductDetails” has some other component integrated with it like “Comment” or “Review” then test cases for the interaction of the “Comment” or “Review” component should be tested in the test cases of “ProductDetails” component and not in “ProductList” component.
Test code coverage: When writing test cases it is important that our test cases cover complete code. E.g. there is an if block that executes when a flag is true and there is an else block that executes when the flag is false. In this case, we need to have 2 separate test cases one covers the code path with a flag set to true and another with a flag set to false. We can generate test coverage reports using a command
ng test --no-watch --code-coverage
The above command will execute test cases and will create a new /coverage directory in the project. We can see a report by opening “index.html” file
We can generate a coverage report each time we test by setting the following option in the Angular CLI configuration file, “angular.json”
"test": {
"options": {
"codeCoverage": true
}
}
Conclusion: While writing Unit test cases for Angular it is important that we cover all code paths. But at the same time, we should write test cases in isolation so that when a test case fails we can find directly a component that is failing. In case there is a dependency on multiple components in test cases, even after finding a failing test case, we will have to further debug and find the dependent component that is causing the problem.
Subscribe to my newsletter
Read articles from NonStop io Technologies directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

NonStop io Technologies
NonStop io Technologies
Product Development as an Expertise Since 2015 Founded in August 2015, we are a USA-based Bespoke Engineering Studio providing Product Development as an Expertise. With 80+ satisfied clients worldwide, we serve startups and enterprises across San Francisco, Seattle, New York, London, Pune, Bangalore, Tokyo and other prominent technology hubs.