【问题标题】:Replace specific module in testing替换测试中的特定模块
【发布时间】:2018-08-10 07:01:54
【问题描述】:

我正在使用 Jest 测试我的 React-Redux 应用程序,作为 API 调用的一部分,我正在导入一个提取模块 cross-fetch。我想用fetch-mock 覆盖或替换它。这是我的文件结构:

Action.js

import fetch from 'cross-fetch';
export const apiCall = () => {
    return fetch('http://url');

Action.test.js

import fetchMock from 'fetch-mock';
import { apiCall } from './Action';
fetchMock.get('*', { hello: 'world' });
describe('actions', () => {
    apiCall().then(
        response => {
            console.log(response)
        })
})

显然,此时我还没有设置测试。因为交叉获取是在更接近它使用它的获取实现的函数处导入的,导致它执行实际调用而不是我的模拟。让 fetch 被模拟的最佳方法是什么(除了删除 import fetch from 'cross-fetch' 行)?

有没有办法根据调用的节点脚本是test 还是build 进行条件导入?还是将模拟提取设置为优先?

【问题讨论】:

  • 运行测试时会发生什么?
  • 当我在不删除import fetchMock 的情况下运行我的代码时,它显示only absolute urls are supported 带有提取
  • 什么代码导致了这个错误?您是否尝试将其更改为绝对 URL?
  • 导入会导致该错误。如果我在其中放入一个实际的 URL,则只需调用该 URL,这显然不是我想要的。如果我删除该导入,它会改用 fetch-mock。

标签: javascript reactjs ecmascript-6 fetch jestjs


【解决方案1】:

如果你的项目是 webpack 项目,那么https://github.com/plasticine/inject-loader 非常有用。只需几行代码,您就可以简单地将任何依赖项与 mock 交换。

describe('MyModule', () => {
  let myModule;
  let dependencySpy;

  beforeEach(() => {
    dependencySpy= // {a mock/spy};
    myModule = require('inject-loader!./MyModule')({
      'cross-fetch': {dependencySpy},
    });
  });

  it('should call fetch', () => {
    myModule.function1();
    expect(dependencySpy.calls.length).toBe(1);
  });

});

注意:确保不要在文件顶部导入被测模块。 require 调用完成了该部分。

【讨论】:

    【解决方案2】:

    为什么不在你的 Action.js 文件中导出一个 maker 函数,该函数将注入 fetch 方法,然后返回实际的 apiCaller:

    Action.js

    // this export allows you to setup an apiCaller
    // with a different fetcher than in the global scope 
    export const makeApiCaller = (fetch) => (url, opts) => fetch(url, opts);
    // this export is your default apiCaller that uses cross-fetch
    export const apiCall = makeApiCaller(fetch);
    

    然后在您的测试中,您可以在某处实例化您的 ApiCaller,例如在before:

    Action.test.js

    import fetchMock from 'fetch-mock';
    import { makeapiCaller } from './Action';
    
    fetchMock.get('*', { hello: 'world' });
    
    // ...
    
    let apiCall; 
    before(() {
      apiCall = makeApiCaller(fetch); // injects mocked fetch 
    });
    
    describe('actions', () => {
        apiCall('/foo/bar').then(
            response => {
                console.log(response)
            })
    })
    

    注意:这样做的好处是,您不必在 apiCall 函数签名中引入另一个参数(如另一个答案中所建议的那样),从而保持向后兼容。

    【讨论】:

      【解决方案3】:

      有几种方法可以解决这个问题

      1. 您可以stub modules without dependency injection using Sinon

      2. 使用名为 rewire 的库在程序中模拟导入的方法 来电

      3. 重新编写你原来的函数,这样你就不会直接使用导入了

        const apiCall = (url, {fetchFn = fetch}) => fetchFn(url);
        
        describe("apiCall", () => {
          it("should call fetch with url", () => {
            const fetchFn = sinon.spy();
            const EXPECTED_URL = "URL";
        
            apiCall(EXPECTED_URL, {fetchFn});
        
            expect(fetchFn.firstCall.args).to.deep.equal([EXPECTED_URL]);
          })
        });
        
      4. 拦截请求并断言响应(这似乎是 fetch-mock 所做的,但我更喜欢 nock,因为文档要好得多)。

        describe("apiCall", () => {
          it("should call fetch with url", () => {
            const EXPECTED_URL = "URL";
            const EXPECTED_RESPONSE = 'domain matched';
        
            var scope = nock(EXPECTED_URL)
            .get('/resource')
            .reply(200, EXPECTED_RESPONSE);
        
            return expect(apiCall(EXPECTED_URL)).to.equal(EXPECTED_RESPONSE);
          })
        });
        

      【讨论】:

        【解决方案4】:

        fetch-mock 不打算替换您正在测试的代码中的fetch() 调用,也不需要更改或删除任何导入。相反,它会在您的测试期间提供模拟响应,以便使用 fetch() 发出的请求会收到已知、可靠的响应。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-07-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-26
          • 2019-08-03
          • 1970-01-01
          相关资源
          最近更新 更多