We use cookies to enhance your experience on the site
CodeWorlds

Mocks and Spies - Techniques of Deception

Mocks are like ninja messengers - they impersonate real objects but are under the control of tests.

jasmine.createSpyObj

1describe('DojoComponent with Mock', () => {
2  let component: DojoComponent;
3  let fixture: ComponentFixture<DojoComponent>;
4  let samuraiServiceSpy: jasmine.SpyObj<SamuraiService>;
5
6  beforeEach(async () => {
7    // Create mock with methods
8    samuraiServiceSpy = jasmine.createSpyObj('SamuraiService', [
9      'getAll',
10      'getById',
11      'create',
12      'update'
13    ]);
14
15    // Set default return values
16    samuraiServiceSpy.getAll.and.returnValue(of([
17      { id: 1, name: 'Musashi' }
18    ]));
19
20    await TestBed.configureTestingModule({
21      imports: [DojoComponent],
22      providers: [
23        { provide: SamuraiService, useValue: samuraiServiceSpy }
24      ]
25    }).compileComponents();
26
27    fixture = TestBed.createComponent(DojoComponent);
28    component = fixture.componentInstance;
29  });
30
31  it('should load samurai on init', () => {
32    fixture.detectChanges();  // ngOnInit
33
34    expect(samuraiServiceSpy.getAll).toHaveBeenCalled();
35    expect(component.samuraiList.length).toBe(1);
36  });
37
38  it('should call create when adding samurai', () => {
39    const newSamurai = { name: 'Hanzo', clan: 'Iga' };
40    samuraiServiceSpy.create.and.returnValue(of({ id: 2, ...newSamurai }));
41
42    component.addSamurai(newSamurai);
43
44    expect(samuraiServiceSpy.create).toHaveBeenCalledWith(newSamurai);
45  });
46});

spyOn - Spying on Methods

1describe('BattleService', () => {
2  let service: BattleService;
3  let loggerService: LoggerService;
4
5  beforeEach(() => {
6    TestBed.configureTestingModule({
7      providers: [BattleService, LoggerService]
8    });
9
10    service = TestBed.inject(BattleService);
11    loggerService = TestBed.inject(LoggerService);
12  });
13
14  it('should log battle result', () => {
15    // Spy on an existing method
16    const logSpy = spyOn(loggerService, 'log');
17
18    service.fight('Musashi', 'Sasaki');
19
20    expect(logSpy).toHaveBeenCalledWith(
21      jasmine.stringMatching(/Battle:/)
22    );
23  });
24
25  it('should call through and track', () => {
26    // Call the real method, but also track it
27    spyOn(loggerService, 'log').and.callThrough();
28
29    service.fight('Musashi', 'Sasaki');
30
31    expect(loggerService.log).toHaveBeenCalled();
32  });
33
34  it('should fake implementation', () => {
35    spyOn(loggerService, 'log').and.callFake((message) => {
36      console.log('FAKE LOG:', message);
37    });
38
39    service.fight('Musashi', 'Sasaki');
40  });
41});

Mock Class

1// mock-samurai.service.ts
2export class MockSamuraiService {
3  private mockData: Samurai[] = [
4    { id: 1, name: 'Test Samurai', clan: 'Test Clan', level: 1 }
5  ];
6
7  getAll(): Observable<Samurai[]> {
8    return of(this.mockData);
9  }
10
11  getById(id: number): Observable<Samurai | undefined> {
12    return of(this.mockData.find(s => s.id === id));
13  }
14
15  create(samurai: Omit<Samurai, 'id'>): Observable<Samurai> {
16    const newSamurai = { id: Date.now(), ...samurai };
17    this.mockData.push(newSamurai);
18    return of(newSamurai);
19  }
20}
21
22// Usage in tests
23TestBed.configureTestingModule({
24  providers: [
25    { provide: SamuraiService, useClass: MockSamuraiService }
26  ]
27});

Testing Dependencies with InjectionToken

1import { InjectionToken } from '@angular/core';
2
3export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');
4
5describe('ConfigurableService', () => {
6  it('should use injected config', () => {
7    const mockConfig: ApiConfig = {
8      baseUrl: 'http://test-api',
9      timeout: 1000
10    };
11
12    TestBed.configureTestingModule({
13      providers: [
14        ConfigurableService,
15        { provide: API_CONFIG, useValue: mockConfig }
16      ]
17    });
18
19    const service = TestBed.inject(ConfigurableService);
20    expect(service.getBaseUrl()).toBe('http://test-api');
21  });
22});
Go to CodeWorlds