为什么我的模拟服务 属性 在 Jasmine 单元测试中未定义?
Why my mocked service property is undefined in Jasmine unit test?
我正在学习 Angular 使用 Jasmine 进行单元测试。我正在关注 documentation`s guide for testing services,但对于教程中的另一项服务,也非常简单。
SUT:
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
}
并注入 MessageService:
import { HeroService } from './hero.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
根据指南,我使用 createSpyObj
模拟了注入服务并调用了 SUT 的 getHeroes
:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
describe('HeroService without Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy: MessageService;
it('#getHeroes should add message to MessageService', () => {
//prepare
messageServiceSpy = jasmine.createSpyObj(
'MessageService',
['add'],
['messages']
);
heroService = new HeroService(messageServiceSpy);
messageServiceSpy.messages = ['one', 'two'];//only difference with the guide
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.messages.length).toBe(3);
expect(messageServiceSpy.messages[2]).toBe('HeroService: fetched heroes');
});
});
因为某些原因,在测试的时候,messageServiceSpy.messages
是undefined
:
TypeError: Cannot read property 'length' of undefined
我做错了什么?
我的最终(Jasmine 3.6)hero.service.spec.ts 文件是:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
describe('HeroService without Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy = jasmine.createSpyObj(
'MessageService',
['add'],
['clear']
);
heroService = new HeroService(messageServiceSpy);
it('#getHeroes should call add() once with string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});
以及 Angular TestBed 支持的版本:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';
describe('HeroService with Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('MessageService', ['add']);
TestBed.configureTestingModule({
// Provide both the service-to-test and its (spy) dependency
providers: [HeroService, { provide: MessageService, useValue: spy }],
});
// Inject both the service-to-test and its (spy) dependency
heroService = TestBed.inject(HeroService);
messageServiceSpy = TestBed.inject(MessageService) as jasmine.SpyObj<
MessageService
>;
});
it('#getHeroes should call add() once with string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});
并且没有 TestBed 的方法:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';
describe('HeroService with Angular testing support', () => {
function setup() {
const messageServiceSpy = jasmine.createSpyObj('MessageService', ['add']);
const heroService = new HeroService(messageServiceSpy);
return { heroService, messageServiceSpy };
}
let heroService: HeroService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
it('#getHeroes should call add() once with string value', () => {
//prepare
const { heroService, messageServiceSpy } = setup();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
const { heroService, messageServiceSpy } = setup();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});
我正在学习 Angular 使用 Jasmine 进行单元测试。我正在关注 documentation`s guide for testing services,但对于教程中的另一项服务,也非常简单。
SUT:
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
}
并注入 MessageService:
import { HeroService } from './hero.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
根据指南,我使用 createSpyObj
模拟了注入服务并调用了 SUT 的 getHeroes
:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
describe('HeroService without Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy: MessageService;
it('#getHeroes should add message to MessageService', () => {
//prepare
messageServiceSpy = jasmine.createSpyObj(
'MessageService',
['add'],
['messages']
);
heroService = new HeroService(messageServiceSpy);
messageServiceSpy.messages = ['one', 'two'];//only difference with the guide
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.messages.length).toBe(3);
expect(messageServiceSpy.messages[2]).toBe('HeroService: fetched heroes');
});
});
因为某些原因,在测试的时候,messageServiceSpy.messages
是undefined
:
TypeError: Cannot read property 'length' of undefined
我做错了什么?
我的最终(Jasmine 3.6)hero.service.spec.ts 文件是:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
describe('HeroService without Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy = jasmine.createSpyObj(
'MessageService',
['add'],
['clear']
);
heroService = new HeroService(messageServiceSpy);
it('#getHeroes should call add() once with string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});
以及 Angular TestBed 支持的版本:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';
describe('HeroService with Angular testing support', () => {
let heroService: HeroService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('MessageService', ['add']);
TestBed.configureTestingModule({
// Provide both the service-to-test and its (spy) dependency
providers: [HeroService, { provide: MessageService, useValue: spy }],
});
// Inject both the service-to-test and its (spy) dependency
heroService = TestBed.inject(HeroService);
messageServiceSpy = TestBed.inject(MessageService) as jasmine.SpyObj<
MessageService
>;
});
it('#getHeroes should call add() once with string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
messageServiceSpy.add.calls.reset();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});
并且没有 TestBed 的方法:
import { MessageService } from './message.service';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';
describe('HeroService with Angular testing support', () => {
function setup() {
const messageServiceSpy = jasmine.createSpyObj('MessageService', ['add']);
const heroService = new HeroService(messageServiceSpy);
return { heroService, messageServiceSpy };
}
let heroService: HeroService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
it('#getHeroes should call add() once with string value', () => {
//prepare
const { heroService, messageServiceSpy } = setup();
//act
heroService.getHeroes();
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched heroes'
);
});
it('#getHero should call add() once with proper string value', () => {
//prepare
const { heroService, messageServiceSpy } = setup();
let id = 1;
//act
heroService.getHero(id);
//assert
expect(messageServiceSpy.add).toHaveBeenCalledTimes(1);
expect(messageServiceSpy.add).toHaveBeenCalledWith(
'HeroService: fetched hero id=' + id
);
});
});