Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spectacular Angular feature tests

Spectacular Angular feature tests

Spectacular is the first Angular testing library to enable integration tests across an entire Angular feature. Feature test suites support refactoring your codebase as they don’t rely on implementation details. Combine Spectacular and Angular Testing Library to test as a user across an entire routed Angular feature. Verify complete user flows in a fraction of the time that it would take using an end-to-end testing framework.

Presented at:
- NG Poland, January 2022
- Angular Vienna, July 2022
- Angular Tiny Conf: Peretor, December 2022

Recording: https://www.youtube.com/watch?v=M65J8pSqU14&t=9265s

Lars Gyrup Brink Nielsen

January 25, 2022
Tweet

More Decks by Lars Gyrup Brink Nielsen

Other Decks in Programming

Transcript

  1. Clear box testing • Intricate awareness of implementation details •

    Drives code design • Documents and exercises low-level behavior • Often used for isolated unit tests Illustration by macrovector
  2. Mystery box testing • Unaware of individual system parts •

    Verifies system behavior • Exercises implementation with little to no test doubles • Often used for end-to-end tests Illustration by macrovector
  3. Implementation details in component tests New up component or use

    Angular testbed? Replace dependencies with test doubles Trigger change detection
  4. Complicated techniques in component tests Shallow or integrated test? Link

    up declarable dependencies Trigger DOM and component events
  5. Trigger event or call component method? Complicated techniques in component

    tests Query the component tree DebugElements or native elements?
  6. End-to-end tests Ease of use Interact and observe as a

    user Reuse test setup Little to no use for test doubles
  7. End-to-end tests Rigidness Might not be able to replace native

    APIs Might not support all native browser features Might not support existing testing toolchains
  8. Spectacular @ngworker/spectacular An Angular integration testing library with 3 APIs:

    • Application testing API • Feature testing API • Pipe testing API
  9. Spectacular Feature testing API Test routed Angular features Provide feature-

    aware navigation services Reusable test root component
  10. Angular feature tests • Test a routed Angular feature as

    a mystery box • Interact and verify as a user • Exercise complete userflows across routes and components Diagram inspired by Martin Fowler
  11. Angular feature tests • Test a routed Angular feature as

    a mystery box • Interact and verify as a user • Exercise complete userflows across routes and components
  12. Angular feature tests • Test a routed Angular feature as

    a mystery box • Interact and verify as a user • Exercise complete userflows across routes and components
  13. Angular feature tests are fast and independent No need for

    a host application Replace dependencies if needed Orders of magnitude faster than E2E tests
  14. it('Edit crisis name from crisis detail', async () => {

    // Arrange 1st route const { location, router, user } = await setup(); const crisisId = 2; await router.navigate(['~', crisisId]); // Act on 1st route await user.clear(await findNameControl()); await user.type(await findNameControl(), 'The global temperature is rising'); await user.click(await findSaveButton()); // Assert on 2nd route expect(await findSelectedCrisis(/the global temperature is rising/i)) .toBeInTheDocument(); expect(location.path()).toBe(`~/;id=${crisisId};foo=foo`); }); Edit crisis from detail page feature test using SIFERS
  15. it('Edit crisis name from crisis center home', async () =>

    { // Arrange 1st route const { router, user } = await setup(); await router.navigateByUrl('~/’); // Act on 1st route await user.click(await findCrisisLink(/procrastinators meeting delayed again/i)); // Act on 2nd route await user.clear(await findNameControl()); await user.type(await findNameControl(), 'Coral reefs are dying'); await user.click(await findSaveButton()); // Assert on 3rd route expect(await findCrisisCenterHomeGreeting()).toBeInTheDocument(); expect( await findSelectedCrisis(/coral reefs are dying/i) ).toBeInTheDocument(); }); Edit crisis from home page feature test using SIFERS
  16. import { screen } from '@testing-library/angular'; import { Matcher }

    from '@testing-library/dom'; const findCrisisCenterGreeting = () => screen.findByText(/welcome to the crisis center/i); const findCrisisLink = (name: Exclude<Matcher, number>) => screen.findByRole('link', { name, }); const findNameControl = () => screen.findByPlaceholderText(/name/i); const findSaveButton = () => screen.findByRole('button', { name: /save/i }); const findSelectedCrisis = (name: Matcher) => screen.findByText(name, { selector: '.selected a', }); Named queries for Edit crisis feature tests
  17. import { provideSpectacularFeatureTest, SpectacularAppComponent, SpectacularFeatureLocation, SpectacularFeatureRouter, } from '@ngworker/spectacular'; import

    { render } from '@testing-library/angular'; import userEvent from '@testing-library/user-event'; import { crisisCenterPath, crisisCenterRoutes } from '@tour-of-heroes/crisis-center'; const setup = async () => { const user = userEvent.setup(); const { fixture: { debugElement: { injector } } } = await render( SpectacularAppComponent, { providers: [ provideSpectacularFeatureTest ({ featurePath: crisisCenterPath, }), ], routes: crisisCenterRoutes, }); return { location: injector.get(SpectacularFeatureLocation), router: injector.get(SpectacularFeatureRouter), user, }; }; SIFERS for Edit crisis feature tests
  18. it('Edit crisis name from crisis detail', () => { setup().then(async

    ({ location, ngZone, router }) => { // Arrange 1st route const crisisId = 2; await ngZone.run(() => router.navigate([crisisCenterPath, crisisId])); // Act on 1st route findNameControl() .clear({ force: true }) .type('The global temperature is rising'); findSaveButton().click(); // Assert on 2nd route findSelectedCrisis(/the global temperature is rising/i) .should('be.visible') .then(() => { expect(location.path()).to.deep.equal( `/${crisisCenterPath};id=${crisisId};foo=foo` ); }); }); }); Edit crisis from detail page feature test using SIFERS
  19. it('Edit crisis name from crisis center home', () => {

    setup().then(() => { // Act on 1st route findCrisisLink(/procrastinators meeting delayed again/i).click(); // Act on 2nd route findNameControl().clear({ force: true }).type('Coral reefs are dying'); findSaveButton().click(); // Assert on 3rd route findCrisisCenterHomeGreeting().should('be.visible'); findSelectedCrisis(/coral reefs are dying/i).should('be.visible'); }); }); Edit crisis from home page feature test using SIFERS
  20. const findCrisisCenterHomeGreeting = () => cy.contains(/welcome to the crisis center/i);

    const findCrisisLink = (name: string | RegExp) => cy.get('a').contains(name); const findNameControl = () => cy.get('[placeholder="name"]'); const findSaveButton = () => cy.get('button').contains(/save/i); const findSelectedCrisis = (name: string | RegExp) => cy.get('.selected a').contains(name); Named queries for Edit crisis feature tests
  21. import { provideSpectacularFeatureTest, SpectacularAppComponent, } from '@ngworker/spectacular’; import { crisisCenterPath,

    crisisCenterRoutes } from '@tour-of-heroes/crisis-center'; const setup = () => cy.mount(SpectacularAppComponent, { imports: [RouterTestingModule.withRoutes([crisisCenterRoute])], providers: [ provideSpectacularFeatureTest({ featurePath: crisisCenterPath }), ], }).then(async({ fixture: { debugElement: { injector } } }) => { const location = injector.get(Location); const ngZone = injector.get(NgZone); const router = injector.get(Router); await ngZone.run(() => router.navigate([crisisCenterPath])); return { location, ngZone, router, }; } ); SIFERS for Edit crisis feature tests
  22. Angular testing strategy • Feature tests can replace most end-to-end

    tests but should not replace all of them • Feature tests should complement unit tests and component tests, not replace all of them • Feature tests do not aid in system design or documentation but support refactoring without breaking tests Illustration from Kent C. Dodds’ ”Testing JavaScript” course
  23. Angular feature tests are Goldilocks’ choice High confidence in desired

    behavior Sufficiently isolated Interact and verify as a user
  24. Angular feature tests are Goldilocks’ choice Shared simple test setup

    Unaware of implementation details Flexible when needed
  25. Thank you for attending Lars Gyrup Brink Nielsen @[email protected] ngworker.github.io/ngworker

    @ngworker/spectacular discord.gg/wwjhWyx7p8 Some icons courtesy of Flaticon