【问题标题】:How to mock private ngxs state service dependency/property in jest unit tests如何在开玩笑的单元测试中模拟私有 ngxs 状态服务依赖项/属性
【发布时间】:2021-08-15 14:38:15
【问题描述】:

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

我刚刚开始测试 angular/node 应用程序,所以我不知道我在做什么。

我按照我从this SO question 那里学到的知识进行了以下测试。

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 中的函数是异步函数,我也不知道如何为函数定义异步返回值。如果有人能对此有所了解,我将不胜感激。

最终解决方案

基于answer given by 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([]);
  });
});

【问题讨论】:

    标签: angular typescript unit-testing jestjs ngxs


    【解决方案1】:

    在您的示例中,您使用 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 服务相同。

    关于您问题的第二部分,如果您在说异步时指的是可观察对象(我可以从您的用法中推断出),那么您可以创建一个可观察对象以作为结果返回。例如:

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

    附言。如果您在说 async 时确实意味着承诺,那么您只需将您的方法标记为 async 即可获得承诺。例如:

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

    【讨论】:

    • 谢谢你。它解决了我遇到的问题,我终于设法让模拟工作。至于重新分配employeesServiceStub,我建议改变将由模拟函数返回的对象。
    猜你喜欢
    • 2020-12-06
    • 2020-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 2019-11-06
    相关资源
    最近更新 更多