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

Test Fest | Angular Unit Tests Distilled

Rainer Hahnekamp
April 25, 2025
150

Test Fest | Angular Unit Tests Distilled

This 45-minute workshop dives into unit testing in Angular with a focus on controlling asynchronous code and mocking dependencies effectively. You'll learn how to write stable, focused tests using both zone-based and zone-less approaches, with examples in both Jasmine and Jest. We'll cover how to mock services without making your tests brittle, and discuss when unit tests are the right tool—and when they’re not. The session wraps up with a Q&A to address your specific questions.

Rainer Hahnekamp

April 25, 2025
Tweet

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. Pros & Cons ✅ Fast ✅ Exploratory ✅ Bugs are

    easy to find ✅ Enforce good Design ⛔ No Refactoring ⛔ High Maintainability ⛔ Low Coverage ⛔ Low Confidence
  3. Component Original Service Mocked Service Component Original Service Mocked Functions

    Mock jest.fn Spy jest.spyOn Component Original Service Stubbed Service Stub
  4. RainerHahnekamp Spy = Mocking in Jasmine • Can be wrapped

    around a function or property • Saves history of calls along their arguments • Returns undefined by default • Allows to replace implementation (fake) dynamically
  5. RainerHahnekamp Spy Strategy - Behaviours spy.and. • stub: default •

    callThrough: uses original implementation • fake: uses alternative implementation • returnValue / returnValues: value or list of values to be returned
  6. RainerHahnekamp Factory methods • spyOn ◦ requires an object ◦

    attaches spy on one method • jasmine.createSpy ◦ used when dealing with functions • jasmine.createSpyObj ◦ creates an object with multiple spied functions
  7. RainerHahnekamp Spy in Action it('should mock with spyOn', () =>

    { const validator = { isValid: (query) => query === 'Domgasse 5' }; const spy = spyOn(validator, 'isValid'); expect(validator.isValid('Domgasse 5')).toBeUndefined(); expect(spy).toHaveBeenCalledWith('Domgasse 5'); spy.and.callThrough(); expect(validator.isValid('Domgasse 5')).toBeTrue(); spy.and.callFake((query) => query === 'Domgasse 15'); expect(validator.isValid('Domgasse 15')).toBeTrue(); expect(validator.isValid('Domgasse 5')).toBeFalse(); spy.and.returnValue(true); expect(validator.isValid('unknown')).toBeTrue(); });
  8. RainerHahnekamp Angular-based Approaches • waitForAsync: automatic done callback • fakeAsync:

    transforms async to sync task ◦ flushMicrotasks: run all microtasks ◦ tick: move forward in time ◦ flush: run all asynchronous tasks (skips periodic timers)
  9. RainerHahnekamp waitForAsync: Automatic done callback test('async', waitForAsync(() => { expect.hasAssertions();

    let a = 1; Promise.resolve().then(() => { a++; expect(a).toBe(2); }); window.setTimeout(() => { a++; expect(a).toBe(3); }, 1000); }) );
  10. RainerHahnekamp fakeAsync: Turn asynchrony into synchrony test("microtasks", fakeAsync(() => {

    let a = 1; Promise.resolve().then(() => (a = 2)); expect(a).toBe(1); flushMicrotasks(); expect(a).toBe(2); }));
  11. RainerHahnekamp fakeAsync test("immediate macrotasks", fakeAsync(() => { let a =

    1; window.setTimeout(() => (a = 2)); expect(a).toBe(1); tick(); expect(a).toBe(2); }));
  12. RainerHahnekamp fakeAsync test("delayed macrotasks", fakeAsync(() => { let a =

    1; window.setTimeout(() => (a = 2), 2000); expect(a).toBe(1); tick(2000); expect(a).toBe(2); }), 1000);
  13. RainerHahnekamp fakeAsync test("delayed macrotasks", fakeAsync(() => { let a =

    1; window.setTimeout(() => (a = 2), 2000); expect(a).toBe(1); flush(); expect(a).toBe(2); }), 1000);
  14. RainerHahnekamp Fake Timers (Jest-only) • Setup & Teardown ◦ jest.useFakeTimers();

    ◦ jest.useRealTimers(); • jest.runAllTimers() ◦ Runs all asynchronous triggered now and in the future ◦ Dangerous for intervals, etc. • jest.runOnlyPendingTimers(); ◦ Runs timers known at the time of execution ◦ Safer than runAllTimers() • jest.runAllTicks() ◦ Runs all MicroTasks • jest.advanceTimersByTime([time]); ◦ Like tick() Use async() to cover Promises
  15. Fakes • Decouple implementation details • Better DX • Existing

    Fakes ◦ HttpTestingController ◦ RouterTestingHarness ◦ Harnesses in general • Additional, initial effort ◦ They have to be written