为了账号安全,请及时绑定邮箱和手机立即绑定

我如何在 React 组件中模拟服务以开玩笑地隔离单元测试?

我如何在 React 组件中模拟服务以开玩笑地隔离单元测试?

皈依舞 2022-12-22 09:56:09
我正在尝试重构一个单元测试,以将使用 axios 调用 API 的服务与调用该服务的组件隔离开来。该服务目前非常简单:import axios from 'axios'export default class SomeService {  getObjects() {    return axios.get('/api/objects/').then(response => response.data);  }}这是调用该服务的组件的片段: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 是否按照我的预期呈现数据:// ...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,这就是事情出错的地方,因为我认为我对正在传递的内容的内部结构了解不够。例如,我认为由于 SomeService 只返回 axios 响应,我可以类似地模拟 SomeService,有点像这样:// ...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:someService.getObjects().then((result) => {我具体需要模拟ObjectList什么,以便组件可以获取SomeService设置其状态所需的内容?
查看完整描述

3 回答

?
倚天杖

TA贡献1828条经验 获得超3个赞

模拟类实例的问题在于,如果没有引用,可能很难访问类实例及其方法。由于someService是组件模块的本地文件,因此无法直接访问。


没有特定的模拟,jest.mock('./SomeService')依赖于 以未指定的方式工作的类自动模拟。该问题表明模拟类的不同实例具有不同的getObjects模拟方法,这些方法不会相互影响,尽管getObjects是原型方法并且符合new SomeService().getObjects === new SomeService().getObjects未模拟类。


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


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断言可能仍然是有益的。


查看完整回答
反对 回复 2022-12-22
?
桃花长相依

TA贡献1860条经验 获得超8个赞

在使用 jest 文档中建议的不同方法进行一些试验和错误尝试后,唯一似乎有效的方法是jest.mock()使用模块工厂参数进行调用,如下所示:


// rename data to start with 'mock' so that the factory can use it

const mock_data = {

  data: require('./test_json/object_list_response.json'),

};


jest.mock('./SomeService', () => {

  return jest.fn().mockImplementation(() => {

    return {

      getObjects: () => {

        return Promise.resolve(mock_data).then(response => response.data)

      }

    };

  });

});


// write tests

使用mockResolvedValue()没有用,因为我无法链接.then()它。


如果这导致任何人找到更优雅或惯用的解决方案,我欢迎其他答案。


查看完整回答
反对 回复 2022-12-22
?
慕的地10843

TA贡献1785条经验 获得超8个赞

对于后代,另一种解决方案是在文件夹中创建手动模拟__mocks__(灵感来自 Estus Flask 的评论和本文档)。


./__mocks__/SomeService.js


export const mockGetObjects = jest.fn()


const mock = jest.fn(() => {

    return {getObjects: mockGetObjects}

})


export default mock

然后普通jest.mock('./SomeService')调用与稍后在测试中定义的实现一起工作:


mockGetObjects.mockImplementationOnce(() => {

  return Promise.resolve(object_data).then(response => response.data)

})


查看完整回答
反对 回复 2022-12-22
  • 3 回答
  • 0 关注
  • 160 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号