Kurs Angular · Moduł 11: Testowanie

Mocki i Spy - Techniki Podstępu

2 min czytania ·
W tej lekcji4

Mocki to jak posłańcy ninja - udają prawdziwe obiekty, ale są pod kontrolą testów.

jasmine.createSpyObj

describe('DojoComponent with Mock', () => {
  let component: DojoComponent;
  let fixture: ComponentFixture<DojoComponent>;
  let samuraiServiceSpy: jasmine.SpyObj<SamuraiService>;

  beforeEach(async () => {
    // Tworzymy mock z metodami
    samuraiServiceSpy = jasmine.createSpyObj('SamuraiService', [
      'getAll',
      'getById',
      'create',
      'update'
    ]);

    // Ustawiamy domyślne zwracane wartości
    samuraiServiceSpy.getAll.and.returnValue(of([
      { id: 1, name: 'Musashi' }
    ]));

    await TestBed.configureTestingModule({
      imports: [DojoComponent],
      providers: [
        { provide: SamuraiService, useValue: samuraiServiceSpy }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(DojoComponent);
    component = fixture.componentInstance;
  });

  it('should load samurai on init', () => {
    fixture.detectChanges();  // ngOnInit

    expect(samuraiServiceSpy.getAll).toHaveBeenCalled();
    expect(component.samuraiList.length).toBe(1);
  });

  it('should call create when adding samurai', () => {
    const newSamurai = { name: 'Hanzo', clan: 'Iga' };
    samuraiServiceSpy.create.and.returnValue(of({ id: 2, ...newSamurai }));

    component.addSamurai(newSamurai);

    expect(samuraiServiceSpy.create).toHaveBeenCalledWith(newSamurai);
  });
});

spyOn - Szpiegowanie Metod

describe('BattleService', () => {
  let service: BattleService;
  let loggerService: LoggerService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [BattleService, LoggerService]
    });

    service = TestBed.inject(BattleService);
    loggerService = TestBed.inject(LoggerService);
  });

  it('should log battle result', () => {
    // Szpiegujemy istniejącą metodę
    const logSpy = spyOn(loggerService, 'log');

    service.fight('Musashi', 'Sasaki');

    expect(logSpy).toHaveBeenCalledWith(
      jasmine.stringMatching(/Battle:/)
    );
  });

  it('should call through and track', () => {
    // Wywołaj prawdziwą metodę, ale też śledź
    spyOn(loggerService, 'log').and.callThrough();

    service.fight('Musashi', 'Sasaki');

    expect(loggerService.log).toHaveBeenCalled();
  });

  it('should fake implementation', () => {
    spyOn(loggerService, 'log').and.callFake((message) => {
      console.log('FAKE LOG:', message);
    });

    service.fight('Musashi', 'Sasaki');
  });
});

Mock Class

// mock-samurai.service.ts
export class MockSamuraiService {
  private mockData: Samurai[] = [
    { id: 1, name: 'Test Samurai', clan: 'Test Clan', level: 1 }
  ];

  getAll(): Observable<Samurai[]> {
    return of(this.mockData);
  }

  getById(id: number): Observable<Samurai | undefined> {
    return of(this.mockData.find(s => s.id === id));
  }

  create(samurai: Omit<Samurai, 'id'>): Observable<Samurai> {
    const newSamurai = { id: Date.now(), ...samurai };
    this.mockData.push(newSamurai);
    return of(newSamurai);
  }
}

// Użycie w teście
TestBed.configureTestingModule({
  providers: [
    { provide: SamuraiService, useClass: MockSamuraiService }
  ]
});

Testowanie Zależności z InjectionToken

import { InjectionToken } from '@angular/core';

export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');

describe('ConfigurableService', () => {
  it('should use injected config', () => {
    const mockConfig: ApiConfig = {
      baseUrl: 'http://test-api',
      timeout: 1000
    };

    TestBed.configureTestingModule({
      providers: [
        ConfigurableService,
        { provide: API_CONFIG, useValue: mockConfig }
      ]
    });

    const service = TestBed.inject(ConfigurableService);
    expect(service.getBaseUrl()).toBe('http://test-api');
  });
});

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jaka jest różnica między jasmine.createSpyObj a spyOn?

  2. 2. Jak ustawić wartość zwracaną przez metodę mocka utworzonego przez createSpyObj?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij test komponentu z zamockowanym serwisem.

  • Układanie w pionie

    Uporządkuj kroki przygotowania zamockowanego serwisu w beforeEach (od góry do dołu):

  • Klikanie w kolejności

    Ułóż linię szpiegującą metodę log istniejącego serwisu: spyOn(loggerService, 'log')

  • Układanie w poziomie

    Ułóż provider podmieniający serwis na klasę mock (jedna linia):

Przydatne artykuły