Angular course · Module 11: Testing
Mocks and Spies - Techniques of Deception
In this lesson4
Mocks are like ninja messengers - they impersonate real objects but are under the control of tests.
jasmine.createSpyObj
describe('DojoComponent with Mock', () => {
let component: DojoComponent;
let fixture: ComponentFixture<DojoComponent>;
let samuraiServiceSpy: jasmine.SpyObj<SamuraiService>;
beforeEach(async () => {
// Create mock with methods
samuraiServiceSpy = jasmine.createSpyObj('SamuraiService', [
'getAll',
'getById',
'create',
'update'
]);
// Set default return values
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 - Spying on Methods
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', () => {
// Spy on an existing method
const logSpy = spyOn(loggerService, 'log');
service.fight('Musashi', 'Sasaki');
expect(logSpy).toHaveBeenCalledWith(
jasmine.stringMatching(/Battle:/)
);
});
it('should call through and track', () => {
// Call the real method, but also track it
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);
}
}
// Usage in tests
TestBed.configureTestingModule({
providers: [
{ provide: SamuraiService, useClass: MockSamuraiService }
]
});Testing Dependencies with 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');
});
});Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is the difference between jasmine.createSpyObj and spyOn?
2. How do you set the return value of a mock method created with createSpyObj?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Complete the component test with a mocked service.
- Vertical ordering
Arrange the steps of preparing a mocked service in beforeEach (top to bottom):
- Click in order
Arrange the line that spies on the log method of an existing service: spyOn(loggerService, 'log')
- Horizontal ordering
Arrange the provider that replaces the service with a mock class (one line):