Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

RainerHahnekamp Repository https://github.com/rainerhahnekamp/eternal/tree/talk/2025-04-21_angular-community_unit-testing/1-init

Slide 4

Slide 4 text

Agenda ● Core Concepts ● Mocks ● Asynchronous Tasks ● Fakes

Slide 5

Slide 5 text

Core Concepts

Slide 6

Slide 6 text

Component Service Service Service Directive Service Component Pipe Service Unit Test

Slide 7

Slide 7 text

Tooling Unit Tests Integration & Component Tests End-to-End (E2E) Tests

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Pros & Cons ✅ Fast ✅ Exploratory ✅ Bugs are easy to find ✅ Enforce good Design ⛔ No Refactoring ⛔ High Maintainability ⛔ Low Coverage ⛔ Low Confidence

Slide 11

Slide 11 text

Kent Dodds' Testing Trophy Logic Fundamental Concepts (Async. & Mocking)

Slide 12

Slide 12 text

Mocks

Slide 13

Slide 13 text

Component Original Service Mocked Service Component Original Service Mocked Functions Mock jest.fn Spy jest.spyOn Component Original Service Stubbed Service Stub

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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(); });

Slide 18

Slide 18 text

Asynchronous Tasks

Slide 19

Slide 19 text

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)

Slide 20

Slide 20 text

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); }) );

Slide 21

Slide 21 text

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); }));

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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);

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

RainerHahnekamp Zoneless & asynchronous tasks? ● fakeAsync & waitForAsync depend on zone.js ● Zoneless available since v18 ?

Slide 27

Slide 27 text

Fakes

Slide 28

Slide 28 text

Younes Jaaidi https://cookbook.marmicode.io/angular/fake-it-till-you-mock-it

Slide 29

Slide 29 text

Fakes ● Decouple implementation details ● Better DX ● Existing Fakes ○ HttpTestingController ○ RouterTestingHarness ○ Harnesses in general ● Additional, initial effort ○ They have to be written

Slide 30

Slide 30 text

More on Testing https://www.angulararchitects.io/en/training/professional-angular-testing-playwright-edition/

Slide 31

Slide 31 text

Thanks