Angular provides a lot of great tools for testing your application. Unfortunately, this wide array of tools and different methods for testing can be confusing when you first start working with the framework. Having an understanding of how Angular’s different testing tools and methods correspond to the traditional levels of automated testing will help you write a successful test framework for your application. It also aids you in getting the most out of the tests you write.
Software Testing Pyramid
The software testing pyramid is a concept originally conceived by Mike Cohn. It proposes that the types of tests you write can be divided based on their level of isolation. The lowest level of the pyramid represents the most isolated tests which are referred to as unit tests because they exercise a single isolated unit of your application. This unit is usually a class in most object oriented languages. The class is tested in complete isolation from its dependencies. Because units of software defined this way are so small and focused, the tests in this case tend to be the easiest to write, understand, and maintain.
In the middle of the pyramid, and at a less isolated level are integration (or service) tests. These tests exercise several units working together to perform a task. The number of dependencies and the depth of the tests may vary, but they always involve multiple units of the application and possibly multiple processes. The more pieces of the application and infrastructure involved, the more complex the tests. As the complexity increases so does the cost to write and maintain these tests.
At the top of the automated testing pyramid, and at the least isolated level are system (or UI) tests. These tests exercise the entire application or a significant portion of the application. They usually employ a framework that allows the tests to interact with the system in a manner very similar to the user. These tests are the most complex and therefore the most costly to write and maintain.
There is also one more type of test that is often depicted just above the top of the pyramid. These are manually executed tests. These are by far the most expensive type of tests to execute and maintain. It is actually the objective of a strong set to automated tests to limit the amount of manual testing necessary for the release of your application. A well designed suite of automated tests helps to minimize the number of manual tests required to ensure an acceptable level of correctness.
The software testing pyramid recommends that a suite of automated tests have a large base of unit tests, followed by less integration tests, and finally only a small number of system tests. The basic idea is to do the least amount of work possible to provide confidence in the correctness of your system on any given build.
Important Safety Tip: The testing pyramid provides a good rule of thumb for the balance of tests at different levels of complexity. However, the exact number and balance of tests will depend greatly on the individual system and critical nature of individual components.
Focusing on the test levels and what you want to achieve with automated testing can help you get the most benefit at the lowest cost for your testing effort. Creating a suite of tests that have a good balance at each of the different testing levels has far-reaching consequences from a cost and maintenance perspective. It can mean the difference between having a reliable and supportive suite of tests or a cumbersome and short-lived testing effort.
Angular Testing Pyramid
There are three basic types of tests provided by Angular and these correspond very nicely to the traditional testing pyramid:
Isolated tests are what would normally be considered unit tests. They test a single unit of code (in this case a TypeScript class) in isolation without reliance on any of the dependencies of that class. Isolated tests do not rely on Angular, its test helpers, or its dependency injection framework. They just use your favorite test framework, runner, and TypeScript. With Angular, this will most likely be Jasmine + Karma. The classes are instantiated and tested directly with all of the dependencies mocked and injected via a constructor during instantiation. There is no need to load any of the Angular ecosystem and the artifact is tested in isolation from framework and template dependencies.
Angular Isolated tests provide the most value of any type of the tests in the pyrmaid because they are the least complex, lowest cost to create, and generally require the least amount of maintenance.
Information and examples: https://angular.io/docs/ts/latest/guide/testing.html#!#isolated-unit-tests
Angular provides a set of tools for integration testing through the use of the TestBed module. TestBed is a suite of tools designed to help you test your Angular artifacts in context with Angular, the dependency injection framework, and your HTML template associated with that particular artifact. It would be extremely difficult to test your Angular components and directives in context with the dependency injection framework and their templates / DOM interactions without these tools.
Although you can tests all types of Angular artifacts using the TestBed module, it is especially effective at testing Directives and Components because it allows interaction with and testing of the template and DOM. TestBed has features that allow you to mock out dependencies and nested components so you can somewhat isolate individual components or directives to suit the breadth of the given integration test. However the setup of these types of tests is much more complex than Isolated tests. They also require that the Angular framework be loaded to test the artifact.
Angular Integrated tests are what would normally be considered integration tests in most languages / testing frameworks. These types of tests go beyond testing a unit in isolation and test it in context with some or all of its dependencies. In general these types of tests are significantly more complex than unit tests to create and maintain. Integrated tests are more complex, have a higher cost, run slower, and require more effort to maintain than their Isolated counterparts.
If the unit under test has complex or critical template / DOM interactions then it would be a very good candidate for the creation of Integrated tests. In generaly you will want to limit the number of these types of tests you create and rely on Isolated tests to exercise as much of your logic as possible. This will increase the cost effectiveness and maintainability of your test suite.
Angular Integrated tests allow the testing of Angular artifacts in the context of the Angular framework and any associated template or DOM interactions. These tests are more costly and complex to create than Isolated tests. Should be used sparingly for testing of complex templates.
Information and examples: https://angular.io/guide/testing#test-a-component-with-an-external-template
Angular provides a Selenium based framework named Protractor which provides a great suite of tools for end-to-end testing. Selenium allows for the codification of browser interactions with your application. These types of tests are intended to test applications in their entirety, requiring you to start the web application before it drives the browser to test the site. These types of tests can be isolated to the UI by mocking calls to web services or can test the entire system including the web services. Each additional level of testing significantly increases the cost, complexity, and performance of your tests.
End-to-end tests allow for testing the entire application from the user interface. The tests are comprehensive but are very expensive to write and maintain.
Information and examples: http://www.protractortest.org/#/
- Rely heavily on Isolated tests to ensure the correctness of your logic and design. Although it is possible that there are bugs in the frameworks and tools you are using, there is a lot more value in thoroughly testing your own code.
- If you find writing Isolated tests to be difficult, take a close look at the class under test. Most of the time when you encounter difficulty writing tests, the cause is actually the design of the class you are testing.
- Use Integrated tests to verify any complex DOM or framework interactions. Since these types of tests are much more expensive, try to use them sparingly and only when they deliver a significant benefit or are required for critical functionality.
- Use system tests to ensure that all of the major components of your application and system are working correctly together. Use as few of these types of tests as possible that provide an acceptable level of confidence in the system. Rely on your strong set of Isolated and Integrated tests to ensure the logic in your application is correct.
- If possible separate your system tests into UI only and full system tests. Remember that as you add complexity to your tests you also add cost. Testing the entire system from end-to-end is significantly more expensive than isolating the UI and testing separately from the service layer.
- When bugs occur (which they will) add tests to ensure that the bug has been resolved and ensure it doesn’t happen again.
- Learn from your mistakes. If certain types of tests are difficult to write or bugs occur in specific parts of the system due to lack of test coverage, don’t be afraid to adapt and change your strategy.
- Create the least expensive test suite possible to ensure an acceptable level of correctness for your system.