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

Zoneless Testing

Zoneless Testing

Angular is on the brink of a major evolution: going zoneless. But with this shift come new questions, particularly around handling asynchronous operations. Make no mistake—both fakeAsync and waitForAsync rely on Zone.js, which means they won’t work as expected in a zoneless world.

In this session, I’ll walk you through where we are today and explore the options we have moving forward. We’ll dive into what the Angular team is planning and what testing frameworks like Jest, Jasmine, and Vitest have to offer in this new context.

If you’re curious about what’s coming and want to ensure your tests remain robust in a zoneless environment, this session is for you.

Video is available at: https://youtu.be/SuyqQmJCGp0

Rainer Hahnekamp

December 12, 2024
Tweet

More Decks by Rainer Hahnekamp

Other Decks in Programming

Transcript

  1. About Me... https://www.youtube.com/ @RainerHahnekamp https://www.ng-news.com https://github.com/softarc-consulting/sheriff • Rainer Hahnekamp ANGULARarchitects.io

    NgRx Team (Trusted Collaborator) • Developer / Trainer / Speaker @RainerHahnekamp Workshops NgRx • Testing • Spring • Quality
  2. RainerHahnekamp A First Test 3a/5 // setup const fixture =

    TestBed.configureTestingModule({ providers: [ provideExperimentalZonelessChangeDetection(), provideHttpClient(), provideHttpClientTesting() }).createComponent(QuizComponent); fixture.componentRef.setInput('id', 1); fixture.autoDetectChanges(true)
  3. RainerHahnekamp A First Test 3a/5 // setup const fixture =

    TestBed.configureTestingModule({ providers: [ provideExperimentalZonelessChangeDetection(), provideHttpClient(), provideHttpClientTesting(), {provide: ComponentFixtureAutoDetect, useValue: false} ], }).createComponent(QuizComponent); fixture.componentRef.setInput('id', 1); fixture.autoDetectChanges(true) Automatic Change Detection enabled in zoneless
  4. RainerHahnekamp A First Test 3b/5 // setup TestBed.configureTestingModule({ providers: [

    provideExperimentalZonelessChangeDetection(), provideHttpClient(), provideHttpClientTesting(), provideRouter( [{ path: 'quiz/:id', component: QuizComponent }], withComponentInputBinding(), ), provideLocationMocks(), ], }); const { fixture } = await RouterTestingHarness.create('/quiz/1');
  5. RainerHahnekamp A First Test 4/5 // setup... // init const

    httpClient = TestBed.inject(HttpTestingController); const req = httpClient.expectOne('/holiday/1/quiz'); req.flush(createQuiz(1)); await fixture.whenStable();
  6. RainerHahnekamp A First Test 5/5 // setup... // init... //

    execution const question = screen.getByLabelText('question'); const status = screen.getByLabelText('quiz-status'); expect(within(status).getByText('Correct: 0')).toBeTruthy(); expect( within(question).getByText( 'What programming language is Angular written in?', ), ).toBeTruthy(); within(question).getByRole('button', { name: 'TypeScript' }).click(); await fixture.whenStable(); expect(within(status).getByText('Correct: 1')).toBeTruthy();
  7. RainerHahnekamp PendingTasks ✅ Official Solution • (rx)resource() ✅ Required for

    SSR ✅ Empowers ComponentFixture::whenStable ⛔ Not a good DX • Overhead ⛔ Timeouts are real • Slow Tests ⛔ Issues with third-party Libraries
  8. RainerHahnekamp Stub Example // implementation @Injectable({providedIn: 'root'}) export class Clock

    { setTimeout(fn: () => void, timeout: number) { setTimeout(fn, timeout) } } // stub TestBed.configureTestingModule({ providers: [ // ... { provide: Clock, useValue: { setTimeout: (fn: () => void) => fn() } }, ], });
  9. RainerHahnekamp Minimal Fake Example export class Clock { readonly #tasks

    = new Map<number, () => void>(); schedule(task: () => void, timeout: number) { const timeoutId = window.setTimeout(() => { task(); this.#tasks.delete(timeoutId) }, timeout); this.#tasks.set(timeout, task) } flushAll() { for (const taskId of this.#tasks.keys()) { clearTimeout(taskId) const task = this.#tasks.get(taskId); assertDefined(task); task(); } } }
  10. RainerHahnekamp jasmine.clock it('should show the next button', async () =>

    { const { fixture, question } = await setup(); const clock = jasmine.clock(); clock.install() within(question).getByRole('button', { name: 'TypeScript' }).click(); clock.tick(1000) await fixture.whenStable() });
  11. RainerHahnekamp ✅ No Quarrels with • Change Detection • DOM

    Events • Asynchronous Tasks ✅ Superior DX ⛔ Real Timeout ⛔ Third-Party Library ⛔ No Unit or Service Tests Cypress Component Testing
  12. RainerHahnekamp Summary • Zoneless & Testing is easier than you

    might think • Careful about asynchronous tasks • It is still experimental, i.e. up for change • Always test as close to the real world as possible