Angular 单元测试模拟服务
Angular unit test mock service
我正在尝试测试一项服务,该服务涉及另外 2 项服务
export class UserService {
private env: EnvConfiguration;
constructor(private userApiService: UserApiService, private envService: EnvService) {
this.envService.load().subscribe(env => {
this.env = env;
});
this.userApiService.rootUrl = this.env.apiUrl;
}
getUserList(): Observable<User[]> {
return this.userApiService.getUsers().pipe(
map(result => result),
catchError(err => { return throwError(err);
})
);
}
}
这是我的测试 class :
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
let envServiceSpy = jasmine.createSpyObj('EnvService', ['load']);
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService, {
provide: EnvService,
useValue: envServiceSpy
}],
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
envServiceSpy = TestBed.inject(EnvService) as jasmine.SpyObj<EnvService>;;
});
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should be created', () => {
const stubValue = "apiUrl: 'http://'";
envServiceSpy.load.and.returnValue(of(stubValue));
expect(service).toBeTruthy();
expect(envServiceSpy.load.calls.mostRecent().returnValue)
.toBe(stubValue);
});
it('should return value from observable', () => {
expect(this.service.getUserList()).toBeTruthy();
});
});
我的问题是我的测试根本没有通过。我的印象是问题来自我的模拟
我不能模拟我的两个服务
这是我的错误:
UserService > should be created
TypeError: Cannot read property 'subscribe' of undefined
at <Jasmine>
at new UserService (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.ts:15:27)
at Object.UserService_Factory [as factory] (ng:///UserService/ɵfac.js:5:10)
at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:17193:42)
at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:16943:1)
at NgModuleRef.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:36329:1)
at TestBedRender3.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3227:1)
at Function.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3110:1)
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:22:23)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
Expected undefined to be truthy.
Error: Expected undefined to be truthy.
at <Jasmine>
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:36:21)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
Expected undefined to be 'apiUrl: 'gttp://''.
Error: Expected undefined to be 'apiUrl: 'gttp://''.
at <Jasmine>
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:38:6)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
我的 EnvService 加载配置和我的 UserApiService 包含如何使用 httpClient 调用 api 的方法
我是用户 Angular 9
我正在更新我的测试:
it('should be created', () => {
let envConfig: EnvConfiguration;
envServiceSpy.load.and.returnValue(of(envConfig));
expect(service).toBeTruthy();
expect(envServiceSpy.load.calls.mostRecent().returnValue)
.toBe(envConfig);
});
但是我有这个错误:
Type 'EnvConfiguration' is missing the following properties from type '{ _isScalar: ExpectedRecursive<boolean>; source: ExpectedRecursive<Observable<any>>; operator: ExpectedRecursive<Operator<any, EnvConfiguration>>; ... 6 more ...; toPromise: ExpectedRecursive<...>; }': _isScalar, source, operator, lift, and 6 more.
您的测试未正确注入 UserApiService
,因此当您调用 getUserList()
时,它会尝试启动未定义的 UserApiService.getUsers()
。
import createSpyObj = jasmine.createSpyObj;
import SpyObj = jasmine.SpyObj;
import {of} from 'rxjs';
// .. Other imports
describe('UserService', () => {
let service: UserService;
let envServiceSpy: SpyObj<EnvService>;
let userApiService: SpyObj<UserApiService>;
let usersMock = [
{id: 1, name: 'Walter White', bestQuote: 'I am the one who knocks.'},
{id: 2, name: 'Jesse Pinkman', bestQuote: 'Yeah, bitch! MAGNETS!'},
];
let envMock = {
apiUrl: 'http://example.com',
};
beforeEach(() => {
// It is a good idea to re-initiate the spy instance after each run so you do not face any weird side-effects.
// That way you also do not need to call `mySpy = TestBed.inject(MyService);`
envServiceSpy = createSpyObj('EnvService', ['load']);
envServiceSpy.load.and.returnValue(of(envMock))
userApiService = createSpyObj('UserApiService', ['getUsers'], ['rootUrl']);
userApiService.getUsers.and.returnValue(of(usersMock));
TestBed.configureTestingModule({
providers: [
UserService,
{provide: EnvService, useValue: envServiceSpy},
{provide: UserApiService, useValue: userApiService},
],
});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should set rootUrl for userApiService on init', () => {
// Considering the `constructor()` did run already due to our initialization in `beforeEach()`
// we can just assert on our expectations
expect(envServiceSpy.load).toHaveBeenCalled();
expect(userApiService.rootUrl).toEqual('http://example.com');
});
// Here we test, that the `getUserList()` method in fact mapped
// the (mocked) response from `getUsers()` properly
it('should retrieve user list ', (done) => {
service.getUserList().subscribe((userList) => {
expect(userList).toEqual(usersMock);
expect(userApiService.getUsers).toHaveBeenCalled();
done();
}, done.fail);
});
xit('TODO: Write a test that performs the call to `getUsers()` which returns an *error*', () => {
});
});
我认为您做错了一些事情,首先您缺少一项服务,UserApiService
,其次您不需要 TestBed
来测试您可以的服务只需创建它的一个新实例并将模拟注入其中。我也不知道你为什么需要这个 HttpTestingController
?在你的服务中无处可去,最后你不应该检查返回了什么负载,而是一旦服务是 initialised/created 就调用它......,本质上我已经采用了你的代码,对其进行了一些重组并且有点猜到你想做什么,关于你的例子还有更多建议可以给你,但这应该让你有一个良好的开端。
describe('UserService', () => {
let service: UserService;
let mockUserApiService: jasmine.SpyObj<UserApiService>;
let mockEnvService: jasmine.SpyObj<EnvService>;
const stubValue = "apiUrl: 'http://'";
beforeEach(() => {
mockUserApiService = jasmine.createSpyObj('UserApiService', ['getUsers', 'rootUrl']);
mockEnvService = jasmine.createSpyObj('EnvService', ['load', 'apiUrl']);
});
describe('when the service is created', () => {
beforeEach(() => {
mockUserApiService.load.and.returnValue(of(stubValue));
mockEnvService.rootUrl = 'https://some-site.com';
service = new UserService(mockUserApiService, mockEnvService);
});
it('should call the environment service', () => {
expect(mockEnvService.load).toHaveBeenCalledTimes(1);
});
describe('getUserList', (done) => {
const users = [] as Users[];
beforeEach(() => {
mockUserApiService.getUsers.and.returnValue(of(users));
service.getUserList();
});
it('should call the users api service', () => {
expect(mockUserApiService.getUsers).toHaveBeenCalled();
});
})
})
});
我正在尝试测试一项服务,该服务涉及另外 2 项服务
export class UserService {
private env: EnvConfiguration;
constructor(private userApiService: UserApiService, private envService: EnvService) {
this.envService.load().subscribe(env => {
this.env = env;
});
this.userApiService.rootUrl = this.env.apiUrl;
}
getUserList(): Observable<User[]> {
return this.userApiService.getUsers().pipe(
map(result => result),
catchError(err => { return throwError(err);
})
);
}
}
这是我的测试 class :
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
let envServiceSpy = jasmine.createSpyObj('EnvService', ['load']);
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService, {
provide: EnvService,
useValue: envServiceSpy
}],
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
envServiceSpy = TestBed.inject(EnvService) as jasmine.SpyObj<EnvService>;;
});
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should be created', () => {
const stubValue = "apiUrl: 'http://'";
envServiceSpy.load.and.returnValue(of(stubValue));
expect(service).toBeTruthy();
expect(envServiceSpy.load.calls.mostRecent().returnValue)
.toBe(stubValue);
});
it('should return value from observable', () => {
expect(this.service.getUserList()).toBeTruthy();
});
});
我的问题是我的测试根本没有通过。我的印象是问题来自我的模拟 我不能模拟我的两个服务
这是我的错误:
UserService > should be created
TypeError: Cannot read property 'subscribe' of undefined
at <Jasmine>
at new UserService (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.ts:15:27)
at Object.UserService_Factory [as factory] (ng:///UserService/ɵfac.js:5:10)
at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:17193:42)
at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:16943:1)
at NgModuleRef.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:36329:1)
at TestBedRender3.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3227:1)
at Function.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3110:1)
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:22:23)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
Expected undefined to be truthy.
Error: Expected undefined to be truthy.
at <Jasmine>
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:36:21)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
Expected undefined to be 'apiUrl: 'gttp://''.
Error: Expected undefined to be 'apiUrl: 'gttp://''.
at <Jasmine>
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:38:6)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
我的 EnvService 加载配置和我的 UserApiService 包含如何使用 httpClient 调用 api 的方法 我是用户 Angular 9
我正在更新我的测试:
it('should be created', () => {
let envConfig: EnvConfiguration;
envServiceSpy.load.and.returnValue(of(envConfig));
expect(service).toBeTruthy();
expect(envServiceSpy.load.calls.mostRecent().returnValue)
.toBe(envConfig);
});
但是我有这个错误:
Type 'EnvConfiguration' is missing the following properties from type '{ _isScalar: ExpectedRecursive<boolean>; source: ExpectedRecursive<Observable<any>>; operator: ExpectedRecursive<Operator<any, EnvConfiguration>>; ... 6 more ...; toPromise: ExpectedRecursive<...>; }': _isScalar, source, operator, lift, and 6 more.
您的测试未正确注入 UserApiService
,因此当您调用 getUserList()
时,它会尝试启动未定义的 UserApiService.getUsers()
。
import createSpyObj = jasmine.createSpyObj;
import SpyObj = jasmine.SpyObj;
import {of} from 'rxjs';
// .. Other imports
describe('UserService', () => {
let service: UserService;
let envServiceSpy: SpyObj<EnvService>;
let userApiService: SpyObj<UserApiService>;
let usersMock = [
{id: 1, name: 'Walter White', bestQuote: 'I am the one who knocks.'},
{id: 2, name: 'Jesse Pinkman', bestQuote: 'Yeah, bitch! MAGNETS!'},
];
let envMock = {
apiUrl: 'http://example.com',
};
beforeEach(() => {
// It is a good idea to re-initiate the spy instance after each run so you do not face any weird side-effects.
// That way you also do not need to call `mySpy = TestBed.inject(MyService);`
envServiceSpy = createSpyObj('EnvService', ['load']);
envServiceSpy.load.and.returnValue(of(envMock))
userApiService = createSpyObj('UserApiService', ['getUsers'], ['rootUrl']);
userApiService.getUsers.and.returnValue(of(usersMock));
TestBed.configureTestingModule({
providers: [
UserService,
{provide: EnvService, useValue: envServiceSpy},
{provide: UserApiService, useValue: userApiService},
],
});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should set rootUrl for userApiService on init', () => {
// Considering the `constructor()` did run already due to our initialization in `beforeEach()`
// we can just assert on our expectations
expect(envServiceSpy.load).toHaveBeenCalled();
expect(userApiService.rootUrl).toEqual('http://example.com');
});
// Here we test, that the `getUserList()` method in fact mapped
// the (mocked) response from `getUsers()` properly
it('should retrieve user list ', (done) => {
service.getUserList().subscribe((userList) => {
expect(userList).toEqual(usersMock);
expect(userApiService.getUsers).toHaveBeenCalled();
done();
}, done.fail);
});
xit('TODO: Write a test that performs the call to `getUsers()` which returns an *error*', () => {
});
});
我认为您做错了一些事情,首先您缺少一项服务,UserApiService
,其次您不需要 TestBed
来测试您可以的服务只需创建它的一个新实例并将模拟注入其中。我也不知道你为什么需要这个 HttpTestingController
?在你的服务中无处可去,最后你不应该检查返回了什么负载,而是一旦服务是 initialised/created 就调用它......,本质上我已经采用了你的代码,对其进行了一些重组并且有点猜到你想做什么,关于你的例子还有更多建议可以给你,但这应该让你有一个良好的开端。
describe('UserService', () => {
let service: UserService;
let mockUserApiService: jasmine.SpyObj<UserApiService>;
let mockEnvService: jasmine.SpyObj<EnvService>;
const stubValue = "apiUrl: 'http://'";
beforeEach(() => {
mockUserApiService = jasmine.createSpyObj('UserApiService', ['getUsers', 'rootUrl']);
mockEnvService = jasmine.createSpyObj('EnvService', ['load', 'apiUrl']);
});
describe('when the service is created', () => {
beforeEach(() => {
mockUserApiService.load.and.returnValue(of(stubValue));
mockEnvService.rootUrl = 'https://some-site.com';
service = new UserService(mockUserApiService, mockEnvService);
});
it('should call the environment service', () => {
expect(mockEnvService.load).toHaveBeenCalledTimes(1);
});
describe('getUserList', (done) => {
const users = [] as Users[];
beforeEach(() => {
mockUserApiService.getUsers.and.returnValue(of(users));
service.getUserList();
});
it('should call the users api service', () => {
expect(mockUserApiService.getUsers).toHaveBeenCalled();
});
})
})
});