如何使用 Frisby 和 Jest 模拟函数以 return 自定义响应?

How to mock a function using Frisby and Jest to return custom response?

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

依赖项 公理:“^0.26.0”, dotenv: "^16.0.0", 表示:“^4.17.2”

devDependencies 飞盘:“^2.1.3”, 开玩笑:“^27.5.1”

当我使用 Jest 进行模拟时,来自 API 的正确响应是 returned,但我不想要它。我想要 return 一个像这样的假结果:{ 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"} ]
];

这是一个经典的丢失引用问题。

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

进行测试

问题在于:当您的测试开始时,您的服务器已经 运行ning。因为你用真正的 fetchBtcCurrency.getCurrency 启动了你的服务器,jest 从这一点开始就不能做任何事情了。您的服务器将继续指向真实模块,而不是模拟模块。

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

测试中对导入的断言之所以有效,是因为该导入是在模拟替换真实文件之后进行的。

您没有共享 appserver 文件,但是如果您在同一个模块上创建服务器 并监听 ,那么那些是“挂在全局”(即:从脚本主体调用,而不是函数的一部分),你必须将它们分开。您将需要一个创建服务器的文件(向其附加任何 route/middleware/etc),并且您将需要一个单独的文件 just 来导入第一个文件并开始收听。

例如:

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 文件。 您不能并行启动服务器。 您将作为测试的一部分启动和停止它 setup/teardown。 这将使 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 足够聪明,知道模拟应该放在第一位。