【问题标题】:How to mock a function using Frisby and Jest to return custom response?如何使用 Frisby 和 Jest 模拟函数以返回自定义响应?
【发布时间】:2022-02-17 16:09:26
【问题描述】:

我正在尝试使用 Frisby 和 Jest 模拟一个函数。 以下是有关我的代码的一些详细信息:

依赖 axios: "^0.26.0", dotenv: "^16.0.0", 快递:“^4.17.2”

dev依赖项 frisby: "^2.1.3", 开玩笑:“^27.5.1”

当我使用 Jest 进行模拟时,会返回来自 API 的正确响应,但我不想要它。我想返回一个像这样的假结果:{ a: 'b' }

如何解决?

我有以下代码:

// (API Fetch file) backend/api/fetchBtcCurrency.js
const axios = require('axios');

const URL = 'https://api.coindesk.com/v1/bpi/currentprice/BTC.json';

const getCurrency = async () => {
  const response = await axios.get(URL);
  return response.data;
};

module.exports = {
  getCurrency,
};
// (Model using fetch file) backend/model/cryptoModel.js
const fetchBtcCurrency = require('../api/fetchBtcCurrency');

const getBtcCurrency = async () => {
  const responseFromApi = await fetchBtcCurrency.getCurrency();
  return responseFromApi;
};

module.exports = {
  getBtcCurrency,
};

// (My test file) /backend/__tests__/cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";

describe("Testing GET /api/crypto/btc", () => {

  beforeEach(() => {
    jest.mock('../api/fetchBtcCurrency');
  });

  it('Verify if returns correct response with status code 200', async () => {
    const fetchBtcCurrency = require('../api/fetchBtcCurrency').getCurrency;
    
    fetchBtcCurrency.mockImplementation(() => (JSON.stringify({ a: 'b'})));

    const defaultExport = await fetchBtcCurrency();
    expect(defaultExport).toBe(JSON.stringify({ a: 'b'})); // This assert works

    await frisby
      .get(`${URL}api/crypto/btc`)
      .expect('status', 200)
      .expect('json', { a: 'b'}); // Integration test with Frisby does not work correctly.
  });
});
Response[
  {
    I hid the lines to save screen space.
  }
 ->>>>>>> does not contain provided JSON [ {"a":"b"} ]
];

【问题讨论】:

    标签: javascript jestjs mocking


    【解决方案1】:

    这是一个经典的丢失参考问题。

    由于您使用 Frisby,通过查看您的测试,您似乎正在并行启动服务器,对吗?您首先使用 npm start 启动服务器,然后使用 npm test 运行测试。

    问题在于:当您的测试开始时,您的服务器已经在运行。由于您使用真正的fetchBtcCurrency.getCurrency 启动了您的服务器,因此从现在开始,您将无法做任何事情。您的服务器将继续指向真实模块,而不是模拟模块。

    查看此插图:https://gist.githubusercontent.com/heyset/a554f9fe4f34101430e1ec0d53f52fa3/raw/9556a9dbd767def0ac9dc2b54662b455cc4bd01d/illustration.svg

    在测试中对导入的断言起作用的原因是因为该导入是在模拟替换真实文件之后进行的。

    您没有共享您的 appserver 文件,但如果您在同一模块上创建服务器并监听,并且这些“挂在全局上”(即:从脚本主体调用,而不是函数的一部分),您必须拆分它们。您需要一个创建服务器的文件(将任何路由/中间件/等附加到它),并且您需要一个单独的文件只是来导入第一个文件并开始监听。

    例如:

    app.js

    const express = require('express');
    const { getCurrency } = require('./fetchBtcCurrency');
    
    const app = express()
    
    app.get('/api/crypto/btc', async (req, res) => {
      const currency = await getCurrency();
    
      res.status(200).json(currency);
    });
    
    module.exports = { app }
    

    server.js

    const { app } = require('./app');
    
    app.listen(4000, () => {
      console.log('server is up on port 4000');
    });
    

    然后,在您的 start 脚本上,运行server 文件。但是,在您的测试中,您导入了 app 文件。 您不会并行启动服务器。您将在测试设置/拆卸过程中启动和停止它。 这将使 jest 有机会在服务器开始侦听之前用模拟模块替换真实模块(此时它会失去对它的控制)

    这样,您的测试可能是:

    cryptoBtc.test.js

    require("dotenv").config();
    const frisby = require("frisby");
    const URL = "http://localhost:4000/";
    const fetchBtcCurrency = require('./fetchBtcCurrency');
    const { app } = require('./app');
    
    jest.mock('./fetchBtcCurrency')
    
    describe("Testing GET /api/crypto/btc", () => {
      let server;
    
      beforeAll((done) => {
        server = app.listen(4000, () => {
          done();
        });
      });
    
      afterAll(() => {
        server.close();
      });
    
      it('Verify if returns correct response with status code 200', async () => {
        fetchBtcCurrency.getCurrency.mockImplementation(() => ({ a: 'b' }));
    
        await frisby
          .get(`${URL}api/crypto/btc`)
          .expect('status', 200)
          .expect('json', { a: 'b'});
      });
    });
    

    请注意,导入的顺序无关紧要。您可以在真实导入下方进行“模拟”。 Jest 很聪明,知道模拟应该是第一位的。

    【讨论】:

    • 另外,请注意将实现模拟为 () => ({ a: 'b' }) 有点幼稚,因为该函数显然是一个异步函数。如果在您的代码中,您在“async/await”流程中使用它,那很好,但是您正在测试实现,并且如果将来某个地方您将其更改为基于 Promise 的方法,它将break,因为 return 没有“then”。您应该始终使用异步函数或返回 Promise 的函数来模拟异步函数。喜欢:mockResolvedValue({ a: 'b' })
    • 非常感谢您,伊娜兄弟!你的解释非常适合我!
    猜你喜欢
    • 1970-01-01
    • 2020-11-20
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2019-10-30
    • 1970-01-01
    • 2020-04-24
    相关资源
    最近更新 更多