-
Notifications
You must be signed in to change notification settings - Fork 6
Testing
Testing of Angular projects can be done on multiple levels.
Unit tests work on the lowest level and test small units like components and services. They are easy to implement and have fast execution times, so they can provide early feedback without incurring high cost.
UI integration tests operate on the running application, automating browser interaction, and hence test not only individual units but the integration of these units including UI. Backend APIs and external services are mocked. UI integration tests require more implementation effort as unit tests and have longer execution times.
System tests are UI tests that test the entire system including backend APIs and external services. They provide feedback if the Angular application works as expected, embedded in the system with APIs and services. In contrast to unit tests and UI integration tests, system tests require time to set up the infrastructure before executing the tests. They tend to be flakier because they depend on external dependencies and working infrastructure.
IMAGE 0
Typically, following the test pyramid, the proportion of tests should put unit tests as a base and have less tests the higher you get in the test pyramid layers. However, some people argue that unit tests do not provide the best cost-benefit ratio for web applications and UI tests should be put into the focus. We do not give a recommendation here as this highly depends on the testing requirements of a project.
For unit testing in Angular projects we use Karma as a test runner in combination with Jasmine as testing framework, because these are the default testing tools provided by Angular. There is extensive documentation on unit testing on the Angular website.
The test runner spawns an HTTP server and generates the test runner HTML file from the tests written in Jasmine (or other testing frameworks like Mocha, QUnit, Chai etc).
On the other hand, the testing framework provides a syntax for assertions in JavaScript / TypeScript. Some testing frameworks, e.g. Jest, include a test runner in their framework.
IMAGE 1
Testing code with RxJS Observables is tricky. Whereas simple scenarios can be tested subscribing to the observable, asserting and then calling the done() callback, this approach has its limitations, especially when the observable emits multiple values or at a certain time. For this reason we recommend to use marble testing to have stable and readable tests with an intuitive visual representation of the stream.
In our example application you can find an example for a marble test.
There are several frameworks on the market for automating UI integration tests for Angular web applications.
- most used framework in angular community
- first stable version in 2017
- own test runner and tool set
- Developed by Microsoft
- not based on selenium webdriver
- limited BrowserStack support
- on the market since 2014
- selenium based
- big feature set with helper methods and syntactic sugar
- less used in the community
- not selenium based
- own url proxy to emulate DOM API and JavaScript into the browser
Comparison
To choose the right framework for testing it always depends on your project needs.
- Which browsers/versions needs to be supported?
- Is mobile support needed?
- Is the feature set sufficient to test all of the business logic?
Criterion | Cypress | Playwright | Webdriver.io | TestCafé |
---|---|---|---|---|
Browser support (and versions) | o | + | ++ | ++ |
Mobile support | ++ | + | ++ | ++ |
Feature set | + | ++ | ++ | + |
Execution time | + | + | o | + |
Migration from Protractor | o | o | ++ | - |
Future-proofness | ++ | + | + | o |
Recommendation
Based on our experiences with the testing frameworks we recommend Playwright for projects with low test requirements (just the most recent browser version needs to be supported etc.) because of the fast test execution and the big feature set.
For project migrations from protractor or if its needed to test specific browser versions we recommend Webdriver.io.
In case there is the need in the project for testing different browsers, operating systems and platforms you can use Browserstack.
The service provides the possibility to execute UI integration tests for the desired capabilities (combination of browser, OS and platform) on real remote devices.
Playwright and Webdriver.io can be used with the BrowserStack Automate service as well.
In case there is the need in the project for layout testing, especially for responsiveness testing, we can recommend the framework Galen.
It's a Java based framework for layout and responsiveness testing providing an own domain-specific language (DSL).
Expectations can be formulated as properties (e.g. height / width) of a certain DOM element and its relations to other elements, e.g., positioning, spacing etc.
Example of a Galen specification (gspec)
@objects
header app-product-master header
title h1
subtitle h2
== Header ==
header:
width 100 % of screen/width
@on desktop
header:
height ~ 279 px
@on mobile
header:
height 350 to 400 px
header.title:
centered horizontally inside header
header.subtitle:
centered horizontally inside header
below header.title
Additionally, it is also possible to connect Galen with Browserstack to perform cross platform and cross browser tests. Related configurations can be done within Galens DSL like in the following example:
Example of a Galen test
@@ set
url http://bs-local.com:4200
browserstack_hub http://${browserstack.username}:${browserstack.key}@hub-cloud.browserstack.com/wd/hub
desktop_size 1280x720
@@ parameterized
| deviceName | tags | capabilities |
| Google Pixel 3 | mobile | --dc.device "Google Pixel 3" --dc.os_version "9.0" --dc.real_mobile true |
| OS X Mojave Chrome 76 | desktop | --size ${desktop_size} --browser chrome --dc.browser_version 76 --dc.os "OS X" --dc.os_version Mojave |
| Win 10 Edge 18 | desktop | --size ${desktop_size} --browser edge --dc.browser_version "18.0" --dc.os Windows --dc.os_version 10 |
Product page on Browserstack - ${deviceName}
selenium grid ${browserstack_hub} --page ${url} --dc.project "angular-styleguide" --dc.browserstack.local true ${capabilities}
check layout-tests/specs/product-master.gspec --include ${tags}
In the layer of UI Integration tests it is needed to mock some dependencies, for example the own backend or other external services.
For these purpose there are a lot of frameworks for mocking an HTTP server. We have two frameworks that we using and recommend.
Can be used for simple scenarios.
Example HTTP Mock Server
mockHttpServer.on({
method: 'GET',
path: '/products',
reply: {
status: 200,
headers: { ...defaultHeaders, ...noCachingHeader },
body: JSON.stringify({ products: result, productCount: result.length }),
},
delay,
});
- Advantages:
- Client libraries already available (java, node/javascript/typescript)
- REST API for all other cases
- Many options for deployment (docker image, war file, inline testing by starting mock server together with test runner)
- Nice dashboard for debugging at runtime
- HTTPS support
Stubbing:
Example HTTP Mock Server
await httpServerMock.mockAnyResponse({
httpRequest: {
method: 'POST',
path: '/Product',
},
httpResponse: {
statusCode: 201,
body: JSON.stringify({ id: 'productId' }),
},
});
Verification:
Example HTTP Mock Server
await httpServerMock.verify({
method: 'POST',
path: '/Product',
headers: expectedRequestHeaders,
body: expectedRequestProduct,
});
Page Objects The goal of using page objects is to abstract any page information away from the actual tests. Ideally, you should store all selectors or specific instructions that are unique for a certain page in a page object, so that you still can run your test after you've completely redesigned your page.
For testing the whole system with all its dependencies, we are using the same frameworks as for UI integration tests, but without any mocking. No separate recommendations needed here.