我该如何在React组件中模拟服务以开玩笑地隔离单元测试? [英] How can I mock a service within a React component to isolate unit tests in jest?

查看:74
本文介绍了我该如何在React组件中模拟服务以开玩笑地隔离单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试重构单元测试,以将使用axios调用API的服务与调用该服务的组件隔离开来.

I'm trying to refactor a unit test to isolate a service that calls an API using axios from the component calling the service.

目前该服务非常简单:

import axios from 'axios'

export default class SomeService {
  getObjects() {
    return axios.get('/api/objects/').then(response => response.data);
  }
}

以下是调用服务的组件的片段:

Here's a snippet of the component that calls the service:

const someService = new SomeService();

class ObjectList extends Component {
  state = {
    data: [],
  }

  componentDidMount() {
    someService.getObjects().then((result) => {
      this.setState({
        data: result,
      });
    });
  }

  render() {
    // render table rows with object data
  }
}

export default ObjectList

我可以通过模拟axios来测试ObjectList是否按预期呈现数据:

I can test that ObjectList renders data as I'd expect by mocking axios:

// ...
jest.mock('axios')

const object_data = {
  data: require('./test_json/object_list_response.json'),
};

describe('ObjectList', () => {
  test('generates table rows from object api data', async () => {

    axios.get.mockImplementationOnce(() => Promise.resolve(object_data));

    const { getAllByRole } = render(
      <MemoryRouter>
        <table><tbody><ObjectList /></tbody></table>
      </MemoryRouter>
    );

    await wait();

    // test table contents

  });
});

一切顺利,没有问题.作为一项主要的学术活动,我试图找出如何模拟SomeService而不是axios的方法,这是发生问题的地方,因为我认为我对所传递的内容的内部知识还不够了解.

Everything passes without issue. As a mostly academic exercise, I was trying to figure out how to mock SomeService instead of axios, which is where things went awry because I think I don't understand enough about the internals of what's getting passed around.

例如,我发现由于SomeService只是返回axios响应,因此我可以类似地模拟SomeService,如下所示:

For example, I figured since SomeService just returns the axios response, I could similarly mock SomeService, sort of like this:

// ...
const someService = new SomeService();

jest.mock('./SomeService')

const object_data = {
  data: require('./test_json/object_list_response.json'),
};

describe('ObjectList', () => {
  test('generates table rows from object api data', async () => {

    someService.getObjects.mockImplementationOnce(() => Promise.resolve(object_data))

// etc.

此操作失败,并显示错误:Error: Uncaught [TypeError: Cannot read property 'then' of undefined],并且错误从ObjectList追溯到此行:

This fails with an error: Error: Uncaught [TypeError: Cannot read property 'then' of undefined], and the error traces back to this line from ObjectList:

someService.getObjects().then((result) => {

我具体需要模拟什么,以便ObjectList组件可以从SomeService获取其设置状态所需的内容?

What specifically do I need to mock so that the ObjectList component can get what it needs to from SomeService to set its state?

推荐答案

模拟类实例的问题在于,如果没有引用,可能很难到达类实例及其方法.由于someService对于组件模块而言是本地的,因此无法直接对其进行访问.

The problem with mocking class instances is that it may be difficult to reach class instance and its methods without having a reference. Since someService is local to component module, it can't be accessed directly.

在没有特定模拟的情况下,jest.mock('./SomeService')依赖于 自动模仿类,它以未指定的方式工作.该问题表明,尽管getObjects是原型方法并符合未模拟类中的new SomeService().getObjects === new SomeService().getObjects,但模拟类的不同实例具有互不影响的不同的getObjects模拟方法.

Without specific mock, jest.mock('./SomeService') relies on class automatic mock that works in unspecified ways. The question shows that different instances of mocked class have different getObjects mocked methods that don't affect each other, despite getObjects is prototype method and conforms to new SomeService().getObjects === new SomeService().getObjects in unmocked class.

解决方案是不依赖自动模拟,而是使其以预期的方式工作.使模拟方法可在类实例外部访问的一种实用方法是将其与模拟模块一起携带.这样,mockGetObjects.mockImplementationOnce将影响现有的someService. mockImplementationOnce表示该方法可以在以后的每个测试中更改实现:

The solution is to not rely on automatic mocking but make it work the way it's expected. A practical way to make mocked method accessible outside class instance is to carry it alongside mocked module. This way mockGetObjects.mockImplementationOnce will affect existing someService. mockImplementationOnce implies that the method can change the implementation later per test:

import { mockGetObjects }, SomeService from './SomeService';

jest.mock('./SomeService', () => {
  let mockGetObjects = jest.fn();
  return {
    __esModule: true,
    mockGetObjects,
    default: jest.fn(() => ({ getObjects: mockGetObjects }))
  };
});

...

mockGetObjects.mockImplementationOnce(...);
// instantiate the component

如果该方法应具有恒定的模拟实现,则可以简化任务,因为可以在jest.mock中指定实现.公开mockGetObjects的断言可能仍然是有益的.

If the method should have constant mocked implementation, this simplifies the task because the implementation can be specified in jest.mock. It may still be beneficial to expose mockGetObjects for assertions.

这篇关于我该如何在React组件中模拟服务以开玩笑地隔离单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆