SlideShare a Scribd company logo
1 of 32
Angular Global Summit 2023
High ROI Testing in
Angular
@chrislydemann
christianlydemann.com
@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
@chrislydemann
christianlydemann.com
Hands Up
- Type in the chat:
- Who here loves to write test?
- Who here spend a lot of time on
testing and wish it was easier?
@chrislydemann
christianlydemann.com
Christian Lüdemann
Angular consultant, speaker, trainer, blogger…
From Denmark 🇩🇰
Blog: https://christianlydemann.com
Github: https://github.com/lydemann
Twitter: @chrislydemann
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
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
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
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…
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
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)
Getting Started With
Cypress Component
Testing
The setup
● Either plain Cypress or Nx
● I recommend using Nx:
○ Schematics for setting up
component testing for a
project
○ Provides an Angular
builder for Cypress
○ Support for monorepos
nx generate @nrwl/angular:cypress-component-configuration --project=todo-app
import { defineConfig } from 'cypress';
import { nxComponentTestingPreset } from
'@nrwl/angular/plugins/component-testing';
export default defineConfig({
component: nxComponentTestingPreset(__filename)
});
"component-test": {
"executor": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/todo-
app/cypress.config.ts",
"testingType": "component",
"skipServe": true,
"devServerTarget": "todo-app:build"
}
}
project.json/angular.json
cypress.config.ts
The Ideal Way To
Write Integration
Tests
@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
@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
@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…
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
Use SIFERS
const setup = (initTodoItems: TodoItem[] = []) => {
return mount(WrapperComponent, {
imports: [
RouterTestingModule.withRoutes([...appRoutes]),
AppModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: CustomLoader,
},
}),
],
}).then(
async ({
fixture: {
debugElement: { injector },
},
}) => {
const ngZone = injector.get(NgZone);
const router = injector.get(Router);
const todoListResourceService = injector.get(TodoListResourcesService);
// or mock service worker
todoListResourceService.getTodos = () => {
return of(initTodoItems);
};
await ngZone.run(() => router.navigate(['']));
return {
ngZone,
router,
injector,
};
},
);
};
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');
}
}
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"
/>
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 { }
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
@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
Let’s write tests for a
todo app…
@chrislydemann
christianlydemann.com
Creating SIFERS
setup
1. Input is todo items
2. Importing
RouterTestingModule and
AppModule
3. Setting mock responses
4. Triggering navigation
5. Returning dependencies needed
in test cases
const setup = (initTodoItems: TodoItem[] = []) => {
return mount(WrapperComponent, {
imports: [
RouterTestingModule.withRoutes([...appRoutes]),
AppModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: CustomLoader,
},
}),
],
}).then(
async ({
fixture: {
debugElement: { injector },
},
}) => {
const ngZone = injector.get(NgZone);
const router = injector.get(Router);
const todoListResourceService = injector.get(TodoListResourcesService);
// or mock service worker
todoListResourceService.getTodos = () => {
return of(initTodoItems);
};
await ngZone.run(() => router.navigate(['']));
return {
ngZone,
router,
injector,
};
},
);
};
Create todo item
it('should create todo item', () => {
setup().then(({}) => {
const title = 'Some title';
cy.get('[data-test=todo-title]').type(title);
const description = 'Some description';
cy.get('[data-test=todo-description]').type(description);
const dueDate = new Date().toLocaleDateString('en-US');
cy.get('[data-test=todo-duedate]').type(dueDate);
cy.get('[data-test=create-todo-submit]').click();
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);
});
});
@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);
});
});
@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
});
});
@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');
});
});
@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
Thanks for watching!
Slides will be shared on my Twitter: @chrislydemann
Questions?
@chrislydemann
christianlydemann.com

More Related Content

Similar to High ROI Testing in Angular.pptx

Keeping code clean
Keeping code cleanKeeping code clean
Keeping code cleanBrett Child
 
Writing Tests with the Unity Test Framework
Writing Tests with the Unity Test FrameworkWriting Tests with the Unity Test Framework
Writing Tests with the Unity Test FrameworkPeter Kofler
 
Progressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioProgressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioKnoldus Inc.
 
Anatomy of a Build Pipeline
Anatomy of a Build PipelineAnatomy of a Build Pipeline
Anatomy of a Build PipelineSamuel Brown
 
Cypress Best Pratices for Test Automation
Cypress Best Pratices for Test AutomationCypress Best Pratices for Test Automation
Cypress Best Pratices for Test AutomationKnoldus Inc.
 
2014 11 20 Drupal 7 -> 8 test migratie
2014 11 20 Drupal 7 -> 8 test migratie2014 11 20 Drupal 7 -> 8 test migratie
2014 11 20 Drupal 7 -> 8 test migratiehcderaad
 
Angular4 getting started
Angular4 getting startedAngular4 getting started
Angular4 getting startedTejinderMakkar
 
Hands On with Selenium and WebDriver
Hands On with Selenium and WebDriverHands On with Selenium and WebDriver
Hands On with Selenium and WebDriverTechWell
 
Quick introduction to Angular 4 for AngularJS 1.5 developers
Quick introduction to Angular 4 for AngularJS 1.5 developersQuick introduction to Angular 4 for AngularJS 1.5 developers
Quick introduction to Angular 4 for AngularJS 1.5 developersPaweł Żurowski
 
3 WAYS TO TEST YOUR COLDFUSION API -
3 WAYS TO TEST YOUR COLDFUSION API - 3 WAYS TO TEST YOUR COLDFUSION API -
3 WAYS TO TEST YOUR COLDFUSION API - Ortus Solutions, Corp
 
3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION APIGavin Pickin
 
Unit Testing - The Whys, Whens and Hows
Unit Testing - The Whys, Whens and HowsUnit Testing - The Whys, Whens and Hows
Unit Testing - The Whys, Whens and Howsatesgoral
 
Javascript Programming according to Industry Standards.pptx
Javascript Programming according to Industry Standards.pptxJavascript Programming according to Industry Standards.pptx
Javascript Programming according to Industry Standards.pptxMukundSonaiya1
 
Angular Intermediate
Angular IntermediateAngular Intermediate
Angular IntermediateLinkMe Srl
 
Foster - Getting started with Angular
Foster - Getting started with AngularFoster - Getting started with Angular
Foster - Getting started with AngularMukundSonaiya1
 

Similar to High ROI Testing in Angular.pptx (20)

Keeping code clean
Keeping code cleanKeeping code clean
Keeping code clean
 
An Overview of Angular 4
An Overview of Angular 4 An Overview of Angular 4
An Overview of Angular 4
 
mean stack
mean stackmean stack
mean stack
 
Writing Tests with the Unity Test Framework
Writing Tests with the Unity Test FrameworkWriting Tests with the Unity Test Framework
Writing Tests with the Unity Test Framework
 
Progressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioProgressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.io
 
Anatomy of a Build Pipeline
Anatomy of a Build PipelineAnatomy of a Build Pipeline
Anatomy of a Build Pipeline
 
Cypress Best Pratices for Test Automation
Cypress Best Pratices for Test AutomationCypress Best Pratices for Test Automation
Cypress Best Pratices for Test Automation
 
2014 11 20 Drupal 7 -> 8 test migratie
2014 11 20 Drupal 7 -> 8 test migratie2014 11 20 Drupal 7 -> 8 test migratie
2014 11 20 Drupal 7 -> 8 test migratie
 
Angular4 getting started
Angular4 getting startedAngular4 getting started
Angular4 getting started
 
Hands On with Selenium and WebDriver
Hands On with Selenium and WebDriverHands On with Selenium and WebDriver
Hands On with Selenium and WebDriver
 
Quick introduction to Angular 4 for AngularJS 1.5 developers
Quick introduction to Angular 4 for AngularJS 1.5 developersQuick introduction to Angular 4 for AngularJS 1.5 developers
Quick introduction to Angular 4 for AngularJS 1.5 developers
 
3 WAYS TO TEST YOUR COLDFUSION API -
3 WAYS TO TEST YOUR COLDFUSION API - 3 WAYS TO TEST YOUR COLDFUSION API -
3 WAYS TO TEST YOUR COLDFUSION API -
 
3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API
 
Frontend training
Frontend trainingFrontend training
Frontend training
 
Cypress for Testing
Cypress for TestingCypress for Testing
Cypress for Testing
 
Angularjs
AngularjsAngularjs
Angularjs
 
Unit Testing - The Whys, Whens and Hows
Unit Testing - The Whys, Whens and HowsUnit Testing - The Whys, Whens and Hows
Unit Testing - The Whys, Whens and Hows
 
Javascript Programming according to Industry Standards.pptx
Javascript Programming according to Industry Standards.pptxJavascript Programming according to Industry Standards.pptx
Javascript Programming according to Industry Standards.pptx
 
Angular Intermediate
Angular IntermediateAngular Intermediate
Angular Intermediate
 
Foster - Getting started with Angular
Foster - Getting started with AngularFoster - Getting started with Angular
Foster - Getting started with Angular
 

Recently uploaded

Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Hyundai Motor Group
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?XfilesPro
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 

Recently uploaded (20)

Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 

High ROI Testing in Angular.pptx

  • 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
  • 3. @chrislydemann christianlydemann.com Hands Up - Type in the chat: - Who here loves to write test? - Who here spend a lot of time on testing and wish it was easier?
  • 4. @chrislydemann christianlydemann.com Christian Lüdemann Angular consultant, speaker, trainer, blogger… From Denmark 🇩🇰 Blog: https://christianlydemann.com Github: https://github.com/lydemann Twitter: @chrislydemann
  • 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)
  • 11. Getting Started With Cypress Component Testing
  • 12. The setup ● Either plain Cypress or Nx ● I recommend using Nx: ○ Schematics for setting up component testing for a project ○ Provides an Angular builder for Cypress ○ Support for monorepos nx generate @nrwl/angular:cypress-component-configuration --project=todo-app import { defineConfig } from 'cypress'; import { nxComponentTestingPreset } from '@nrwl/angular/plugins/component-testing'; export default defineConfig({ component: nxComponentTestingPreset(__filename) }); "component-test": { "executor": "@nrwl/cypress:cypress", "options": { "cypressConfig": "apps/todo- app/cypress.config.ts", "testingType": "component", "skipServe": true, "devServerTarget": "todo-app:build" } } project.json/angular.json cypress.config.ts
  • 13. The Ideal Way To Write Integration Tests
  • 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
  • 18. Use SIFERS const setup = (initTodoItems: TodoItem[] = []) => { return mount(WrapperComponent, { imports: [ RouterTestingModule.withRoutes([...appRoutes]), AppModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: CustomLoader, }, }), ], }).then( async ({ fixture: { debugElement: { injector }, }, }) => { const ngZone = injector.get(NgZone); const router = injector.get(Router); const todoListResourceService = injector.get(TodoListResourcesService); // or mock service worker todoListResourceService.getTodos = () => { return of(initTodoItems); }; await ngZone.run(() => router.navigate([''])); return { ngZone, router, injector, }; }, ); };
  • 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
  • 24. Let’s write tests for a todo app…
  • 25. @chrislydemann christianlydemann.com Creating SIFERS setup 1. Input is todo items 2. Importing RouterTestingModule and AppModule 3. Setting mock responses 4. Triggering navigation 5. Returning dependencies needed in test cases const setup = (initTodoItems: TodoItem[] = []) => { return mount(WrapperComponent, { imports: [ RouterTestingModule.withRoutes([...appRoutes]), AppModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: CustomLoader, }, }), ], }).then( async ({ fixture: { debugElement: { injector }, }, }) => { const ngZone = injector.get(NgZone); const router = injector.get(Router); const todoListResourceService = injector.get(TodoListResourcesService); // or mock service worker todoListResourceService.getTodos = () => { return of(initTodoItems); }; await ngZone.run(() => router.navigate([''])); return { ngZone, router, injector, }; }, ); };
  • 26. Create todo item it('should create todo item', () => { setup().then(({}) => { const title = 'Some title'; cy.get('[data-test=todo-title]').type(title); const description = 'Some description'; cy.get('[data-test=todo-description]').type(description); const dueDate = new Date().toLocaleDateString('en-US'); cy.get('[data-test=todo-duedate]').type(dueDate); cy.get('[data-test=create-todo-submit]').click(); 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); }); });
  • 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