如何在 jest 单元测试中模拟私有 ngxs 状态服务 dependency/property
How to mock private ngxs state service dependency/property in jest unit tests
我正在使用 ngxs 来管理我的应用程序的状态。
@State<EmployeesStateModel>({
name: 'employees',
defaults: {
// ...
}
})
@Injectable({
providedIn: 'root'
})
export class EmployeesState {
constructor(private employeesService: EmployeesService) {
}
@Action(GetEmployeesList)
async getEmployeesList(ctx: StateContext<EmployeesStateModel>, action: GetEmployeesList) {
const result = await this.employeesService
.getEmployeeListQuery(0, 10).toPromise();
// ...
}
}
问题
我不明白如何在我的测试中使用 jest 来模拟 EmployeesService
依赖。与 NGXS 测试相关的文档也没有提供任何示例。
I'm just getting started with testing for angular/node applications so I have no idea what I'm doing.
我按照从 中学到的知识进行了以下测试。
describe('EmployeesStateService', () => {
let store: Store;
let employeesServiceStub = {} as EmployeesService;
beforeEach(() => {
employeesServiceStub = {
getEmployeeListQuery: jest.fn()
};
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
NgxsModule.forRoot([EmployeesState])
],
providers: [
{ provide: EmployeesService, useFactory: employeesServiceStub }
]
});
store = TestBed.inject(Store);
TestBed.inject(EmployeesService);
});
it('gets a list of employees', async () => {
employeesServiceStub = {
getEmployeeListQuery: jest.fn((skip, take) => [])
};
await store.dispatch(new GetEmployeesList()).toPromise();
const list = store.selectSnapshot(state => state.employees.employeesList);
expect(list).toStrictEqual([]);
});
});
当我尝试 运行 测试时,这会导致错误 TypeError: provider.useFactory.apply is not a function
。
此外,当我在 beforeEach
函数中设置 employeesServiceStub
的值时,它会抛出一个错误,指出我分配的值缺少实际 [=] 中的剩余属性15=]。本质上是要求我对该服务进行完整的模拟实施。这对我来说效率很低,因为在每次测试中,我都需要为不同的功能定义不同的模拟实现。
TS2740: Type '{ getEmployeeListQuery: Mock ; }' is missing the following properties from type 'EmployeesService': defaultHeaders, configuration, encoder, basePath, and 8 more.
理想情况下,在每个测试中,我应该能够在每个测试中为我的 EmployeesService
的模拟函数定义不同的 return 值,而不必定义我不使用的函数的模拟版本不需要那个测试。
由于 EmployeesService
中的函数是异步函数,我也不知道如何为函数定义异步 return 值。如果有人能对此有所了解,我将不胜感激。
最终解决方案
基于 ,我进行了以下更改,从而解决了我的问题。
describe('EmployeesStateService', () => {
let store: Store;
// Stub function response object that I will mutate in different tests.
let queryResponse: QueryResponseDto = {};
let employeesServiceStub = {
// Ensure that the stubbed function returns the mutatable object.
// NOTE: This function is supposed to be an async function, so
// the queryResponse object must be returned by the of() function
// which is part of rxjs. If your function is not supposed to be async
// then no need to pass it to the of() function from rxjs here.
// Thank you again Mark!
getEmployeesListQuery: jest.fn((skip, take) => of(queryResponse))
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
NgxsModule.forRoot([EmployeesState])
],
providers: [
// Correctly use the useFactory option.
{ provide: EmployeesService, useFactory: () => employeesServiceStub }
]
});
store = TestBed.inject(Store);
TestBed.inject(EmployeesService);
});
it('gets a list of employees', async () => {
// Here I mutate the response object that the stubbed service will return
queryResponse = {
// ...
};
await store.dispatch(new GetEmployeesList()).toPromise();
const list = store.selectSnapshot(state => state.employees.employeesList);
expect(list).toStrictEqual([]);
});
});
您使用 useFactory
的提供者定义在您的示例中不正确。
您可以将其更改为:
providers: [
{ provide: EmployeesService, useFactory: () => employeesServiceStub }
]
您可以使用 useValue
作为您的提供者,但这意味着您无法重新分配您在 beforeEach
中初始化的模拟,而是必须对其进行变异:
providers: [
{ provide: EmployeesService, useValue: employeesServiceStub }
]
// then in your test...
employeesServiceStub..getEmployeeListQuery = jest.fn(....
employeesServiceStub
的重新分配实际上可能仍然是您的测试的问题,因此您可以改变对象,或者将 TestBed 设置移到您的测试中。
注意:为 NGXS 状态模拟提供程序与任何其他 Angular 服务相同。
关于你问题的第二部分,如果你说异步时指的是一个可观察对象(我可以从你的用法中推断出来),那么你可以创建一个可观察到 return 作为结果。例如:
import { of } from 'rxjs';
// ...
employeesServiceStub.getEmployeeListQuery = jest.fn((skip, take) => of([]))
PS。如果您在说异步时确实是指承诺,那么您只需将方法标记为 async
即可获得承诺。例如:
employeesServiceStub.getEmployeeListQuery = jest.fn(async (skip, take) => [])
我正在使用 ngxs 来管理我的应用程序的状态。
@State<EmployeesStateModel>({
name: 'employees',
defaults: {
// ...
}
})
@Injectable({
providedIn: 'root'
})
export class EmployeesState {
constructor(private employeesService: EmployeesService) {
}
@Action(GetEmployeesList)
async getEmployeesList(ctx: StateContext<EmployeesStateModel>, action: GetEmployeesList) {
const result = await this.employeesService
.getEmployeeListQuery(0, 10).toPromise();
// ...
}
}
问题
我不明白如何在我的测试中使用 jest 来模拟 EmployeesService
依赖。与 NGXS 测试相关的文档也没有提供任何示例。
I'm just getting started with testing for angular/node applications so I have no idea what I'm doing.
我按照从
describe('EmployeesStateService', () => {
let store: Store;
let employeesServiceStub = {} as EmployeesService;
beforeEach(() => {
employeesServiceStub = {
getEmployeeListQuery: jest.fn()
};
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
NgxsModule.forRoot([EmployeesState])
],
providers: [
{ provide: EmployeesService, useFactory: employeesServiceStub }
]
});
store = TestBed.inject(Store);
TestBed.inject(EmployeesService);
});
it('gets a list of employees', async () => {
employeesServiceStub = {
getEmployeeListQuery: jest.fn((skip, take) => [])
};
await store.dispatch(new GetEmployeesList()).toPromise();
const list = store.selectSnapshot(state => state.employees.employeesList);
expect(list).toStrictEqual([]);
});
});
当我尝试 运行 测试时,这会导致错误 TypeError: provider.useFactory.apply is not a function
。
此外,当我在 beforeEach
函数中设置 employeesServiceStub
的值时,它会抛出一个错误,指出我分配的值缺少实际 [=] 中的剩余属性15=]。本质上是要求我对该服务进行完整的模拟实施。这对我来说效率很低,因为在每次测试中,我都需要为不同的功能定义不同的模拟实现。
TS2740: Type '{ getEmployeeListQuery: Mock ; }' is missing the following properties from type 'EmployeesService': defaultHeaders, configuration, encoder, basePath, and 8 more.
理想情况下,在每个测试中,我应该能够在每个测试中为我的 EmployeesService
的模拟函数定义不同的 return 值,而不必定义我不使用的函数的模拟版本不需要那个测试。
由于 EmployeesService
中的函数是异步函数,我也不知道如何为函数定义异步 return 值。如果有人能对此有所了解,我将不胜感激。
最终解决方案
基于
describe('EmployeesStateService', () => {
let store: Store;
// Stub function response object that I will mutate in different tests.
let queryResponse: QueryResponseDto = {};
let employeesServiceStub = {
// Ensure that the stubbed function returns the mutatable object.
// NOTE: This function is supposed to be an async function, so
// the queryResponse object must be returned by the of() function
// which is part of rxjs. If your function is not supposed to be async
// then no need to pass it to the of() function from rxjs here.
// Thank you again Mark!
getEmployeesListQuery: jest.fn((skip, take) => of(queryResponse))
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
NgxsModule.forRoot([EmployeesState])
],
providers: [
// Correctly use the useFactory option.
{ provide: EmployeesService, useFactory: () => employeesServiceStub }
]
});
store = TestBed.inject(Store);
TestBed.inject(EmployeesService);
});
it('gets a list of employees', async () => {
// Here I mutate the response object that the stubbed service will return
queryResponse = {
// ...
};
await store.dispatch(new GetEmployeesList()).toPromise();
const list = store.selectSnapshot(state => state.employees.employeesList);
expect(list).toStrictEqual([]);
});
});
您使用 useFactory
的提供者定义在您的示例中不正确。
您可以将其更改为:
providers: [
{ provide: EmployeesService, useFactory: () => employeesServiceStub }
]
您可以使用 useValue
作为您的提供者,但这意味着您无法重新分配您在 beforeEach
中初始化的模拟,而是必须对其进行变异:
providers: [
{ provide: EmployeesService, useValue: employeesServiceStub }
]
// then in your test...
employeesServiceStub..getEmployeeListQuery = jest.fn(....
employeesServiceStub
的重新分配实际上可能仍然是您的测试的问题,因此您可以改变对象,或者将 TestBed 设置移到您的测试中。
注意:为 NGXS 状态模拟提供程序与任何其他 Angular 服务相同。
关于你问题的第二部分,如果你说异步时指的是一个可观察对象(我可以从你的用法中推断出来),那么你可以创建一个可观察到 return 作为结果。例如:
import { of } from 'rxjs';
// ...
employeesServiceStub.getEmployeeListQuery = jest.fn((skip, take) => of([]))
PS。如果您在说异步时确实是指承诺,那么您只需将方法标记为 async
即可获得承诺。例如:
employeesServiceStub.getEmployeeListQuery = jest.fn(async (skip, take) => [])