如何在jest单元测试中模拟私有ngxs状态服务依赖项/属性



我正在使用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测试相关的文档也没有提供任何示例。

我刚开始测试角度/节点应用程序,所以我不知道我在做什么。

我遵循了从这个SO问题中学到的知识,并进行了以下测试。

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的值时,它会抛出一个错误,说我分配的值缺少实际EmployeesService中的剩余属性。本质上是要求我对服务进行一个完整的模拟实现。这对我来说效率很低,因为在每次测试中,我都需要为不同的函数定义一个不同的模拟实现。

TS2740: Type '{ getEmployeeListQuery: Mock ; }' is missing the following properties from type 'EmployeesService': defaultHeaders, configuration, encoder, basePath, and 8 more.

理想情况下,在每次测试中,我应该能够为每次测试中EmployeesService的模拟函数定义不同的返回值,而不必定义该测试不需要的函数的模拟版本。

由于EmployeesService中的函数是异步函数,我也不知道如何定义函数的异步返回值。如果有人能阐明这一点,我将不胜感激。

最终解决方案

根据Mark Whitfield给出的答案,我做出了以下更改,最终解决了我的问题。

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中初始化的mock,而是必须对其进行变异:

providers: [
{ provide: EmployeesService, useValue: employeesServiceStub }
]
// then in your test...
employeesServiceStub..getEmployeeListQuery = jest.fn(....

employeesServiceStub的重新分配实际上可能仍然是测试的一个问题,因此您可以更改对象,或者将TestBed设置移动到测试中。

注意:模拟NGXS状态的提供者与任何其他Angular服务一样。

关于你问题的第二部分,如果你说async时指的是可观察的(我可以从你的用法中推断出(,那么你可以创建一个可观察的结果来返回。例如:

import { of } from 'rxjs';
// ...
employeesServiceStub.getEmployeeListQuery = jest.fn((skip, take) => of([]))

PS。如果你说async时确实意味着promise,那么你可以把你的方法标记为async,这样就可以得到一个promise。例如:

employeesServiceStub.getEmployeeListQuery = jest.fn(async (skip, take) => [])

最新更新