The document discusses high ROI testing strategies in Angular applications. It recommends focusing testing efforts on integration tests using Cypress component testing. The speaker demonstrates how to set up Cypress component testing for an Angular application and write tests that follow best practices. Tests for common CRUD operations on a todo application are shown as examples, covering creating, showing, updating, and deleting todo items.
1. Angular Global Summit 2023
High ROI Testing in
Angular
@chrislydemann
christianlydemann.com
2. @chrislydemann
christianlydemann.com
What we’re going to cover
● My testing story
● Introduction: The high ROI testing strategy
● Cypress component testing
● Best practices
● Demo: Testing the CRUD use cases in a todo app
5. Angular Blog
Check out my blog for practical Angular knowledge:
https://christianlydemann.com/blog
✅ New content every week
✅ Only practical and applicable knowledge
✅ Join more than 50.000+ monthly visitors
@chrislydemann
christianlydemann.com
6. Angular Architect Accelerator
✅ 8-weeks online Angular architecture
course
✅ Teaches you everything you need to
know to work as a high end Angular
architect
✅ End result: Get promoted, get remote
job or become a highly paid freelancer
with the course
Join the free warmup workshop before
next cohort:
christianlydemann.com/accelerator
@chrislydemann
christianlydemann.com
7. The Background For This Talk
● A lot of Angular devs are experiencing testing problems
● Either they are spending a lot of time on testing with little
value in return or omitting writing tests all together
● Traditionally we have been taught about the testing pyramid
and how unit tests should be the bedrock of the testing
strategy…
Christian Lüdemann
christianlydemann.com
8. Lessons Learned
From 10.000 Unit
Tests In A Project
● While building a big platform for
banking apps a team of 50+
developers managed to write
10.000 to cover everything
● The unit tests provided very
little confidence in use cases
actually working thus still had to
overly rely on manual testing
● We found errors are usually
found in the integration points
so we had to change the
strategy…
9. Going Towards
Integration Tests
● Made famous in the React world,
“Write tests, not too many, mostly
integration” seemed like a solution to
our problem
● At that time, our best tool was Jest and
render the full route of a component
with all dependencies
○ Problematic with async timing
and JSDOM
○ Still gave more confidence
with less effort
● Now we have Cypress component tests
that overcome these problems
10. The High ROI Testing Strategy
1. Write few E2E smoke tests (Cypress)
2. Cover use cases with integration tests
(Cypress component testing)
3. Cover edge cases and calculations with
unit tests (Jest)
4. Static: Type everything and use strict
mode (Eslint and Typescript)
14. @chrislydemann
christianlydemann.com
Integration Testing Best Practices
● Run in the same browser as your users
(usually chromium based)
● Render component through route
● Use SIFERS for a simple and
independant test setup
● Render component under test in a
wrapper component
● Use a dedicated test selector
attribute, data-test
● Use standalone components
● Act and assert with the edges of
the systems; minimal stubs
● We focus on the use cases
15. @chrislydemann
christianlydemann.com
Run in the same browser as your users
● Out of the box Cypress supports the two Chromium browsers Chrome and
Electron
● Most users uses Chrome so that is closer to the real users vs. JSDOM
● The test would run in the same browser as real app = higher confidence and less
brittle
16. @chrislydemann
christianlydemann.com
Render component through route
● To make the test as close as the implementation as possible, render routed
components using router setup + navigate
● Will trigger guards and resolvers as in the real app
● More on how to do this in the code examples in the end…
17. Use SIFERS
● Acronym for: Simple, Injectable, Functions, Explicitly, Returning, State
● A setup function that:
1. Takes in the necessary init parameters
2. Does the mounting and setup of dependencies
3. Returns variables needed for the test
● Replaces beforeEach setup logic
● Solves the common problem of having to move all initialization logic to the
specific test cases because a test requires a certain init configuration
19. Use a wrapper component
● Rendering a wrapper component can
both contain router-outlet for routed
component
● Configure dependencies before the
component under test is loaded
@Component({
selector: 'app-wrapper-component',
template: '<router-outlet></router-outlet>',
})
class WrapperComponent {
constructor(translateService: TranslateService) {
(window as any).config = config;
translateService.addLangs(['en']);
translateService.setDefaultLang('en');
}
}
20. Use a dedicated test selector attribute
● Use a dedicated test attribute for
selecting elements in a test, eg.
data-test=”save-button”
● Makes it clear the element is used in
a test and the selector doesn’t
change for other reasons eg. id and
class
<input type="text" required name="todo-title"
[(ngModel)]="currentTodo.title"
class="form-control" data-test="todo-title"
/>
21. Use standalone
components
● Since Angular 14 components
can now import all needed
dependencies
● Mounting a component will
automatically contain all needed
dependencies
● No more need for NgModules
@Component({
selector: 'app-todo-list',
templateUrl: './todo-
list.component.html',
standalone: true,
imports: [SharedModule,
DuedateTodayCountPipe],
})
export class TodoListComponent { }
22. Act and assert with the edges of the systems
● To get the highest confidence we act and assert like our real users would: on the
DOM
● By interacting with the edges of the system we black box test and make sure to
exercise as much code as possible
● Sometimes assertions on network requests can make sense
23. @chrislydemann
christianlydemann.com
Focus on the use cases
● Focus on completing a full use case
● Multiple assertions in a test case is fine
● Describe the tests in the domain language rather than technical jargon
● Eg. it should create todo item vs. it should save todo item in the store
27. @chrislydemann
christianlydemann.com
Show todo item
it('should show todo item', () => {
const title = 'Item to show';
const description = 'This item should be shown';
const dueDate = new Date().toLocaleDateString('en-US');
setup([
{
id: '1',
title,
description,
dueDate,
} as TodoItem,
]).then(({}) => {
cy.get('[data-test=todo-item]').shadow().contains(title);
cy.get('[data-test=todo-item]').shadow().contains(description);
const formattedDueDate = formatDate(dueDate, 'shortDate', 'en-US');
cy.get('[data-test=todo-item]').shadow().contains(formattedDueDate);
});
});
28. @chrislydemann
christianlydemann.com
Update todo item
it('should update todo item', () => {
const title = 'Item to edited';
const description = 'This item should be edited';
const dueDate = new Date().toLocaleDateString('en-US');
setup([
{
id: '1',
title,
description,
dueDate,
} as TodoItem,
]).then(({}) => {
cy.get('[data-test=todo-item]')
.shadow()
.get('[data-test="edit-button"]')
.click();
const updatedTitle = 'Edited title';
cy.get('[data-test=todo-title]').clear().type(updatedTitle);
const updatedDescription = 'Edited description';
cy.get('[data-test=todo-description]').clear().type(updatedDescription);
const currentDate = new Date();
const updatedDueDate = new Date(
currentDate.setDate(currentDate.getDate() + 1),
).toLocaleDateString('en-US');
cy.get('[data-test=todo-duedate]').clear().type(updatedDueDate);
cy.get('[data-test=create-todo-submit]').click();
// Assert is updated
});
});
29. @chrislydemann
christianlydemann.com
Delete todo item
it('should delete todo item', () => {
const title = 'Item to delete';
const description = 'This item should be deleted';
setup([
{
title,
description,
} as TodoItem,
]).then(({}) => {
cy.get('[data-test=todo-item]').shadow().contains(title);
cy.get('[data-test=todo-
item]').shadow().contains(description);
cy.get('[data-test=todo-item]')
.shadow()
.get('[data-test="delete-button"]')
.click();
cy.get('[data-test=todo-item]').should('not.exist');
});
});
30.
31. @chrislydemann
christianlydemann.com
Wrapping up
🎯 Focus on integration tests - Cypress Component test is a great tool for this
✅ Follow the best practices
😎 Start enjoying less time writing tests and higher confidence in them
32. Thanks for watching!
Slides will be shared on my Twitter: @chrislydemann
Questions?
@chrislydemann
christianlydemann.com