我能使用Jest模拟带有特定参数的函数吗?

150
我希望使用Jest模拟一个函数,但仅在它被调用时具有特定参数,例如:
function sum(x, y) {
  return x + y;
}
    
// mock sum(1, 1) to return 4
sum(1, 1) // returns 4 (mocked)
sum(1, 2) // returns 3 (not mocked)

在 Ruby 的 RSpec 库中实现了类似的功能:

class Math
  def self.sum(x, y)
    return x + y
  end
end

allow(Math).to receive(:sum).with(1, 1).and_return(4)
Math.sum(1, 1) # returns 4 (mocked)
Math.sum(1, 2) # returns 3 (not mocked)

我在测试中想要实现更好的解耦,比如说我想要测试一个依赖于sum函数的函数:

function sum2(x) {
  return sum(x, 2);
}

// I don't want to depend on the sum implementation in my tests, 
// so I would like to mock sum(1, 2) to be "anything I want", 
// and so be able to test:

expect(sum2(1)).toBe("anything I want");

// If this test passes, I've the guarantee that sum2(x) is returning
// sum(x, 2), but I don't have to know what sum(x, 2) should return

我知道可以通过类似以下方式来实现:

sum = jest.fn(function (x, y) {
  if (x === 1 && y === 2) {
    return "anything I want";
  } else {
    return sum(x, y);
  }
});

expect(sum2(1)).toBe("anything I want");

但如果我们有一些糖函数来简化它,那就太好了。

这听起来合理吗?Jest中是否已经有了这个功能?

感谢您的反馈。


我也曾面临这个问题,但最后找到了一个非常简单的解决方案:const functionResult = myFunc(args); expect(functionResult).toBe('whatever'); - paddotk
6个回答

118

我发现我的同事最近编写了这个库:jest-when

import { when } from 'jest-when';

const fn = jest.fn();
when(fn).calledWith(1).mockReturnValue('yay!');

const result = fn(1);
expect(result).toEqual('yay!');

这是该库的链接: https://github.com/timkindberg/jest-when


70

jest-whenSteve Shary提到,可能是最好的选择。

如果您不想安装新库,并且不关心原始函数,这里有一个一行代码的解决方案:

sum.mockImplementation((x, y) => x === 1 && y === 2 && "my-return-value")

3
感谢您提供一种不需要外部库的替代方案。 - theberzi
1
如果已知调用的顺序,那么多次链接.mockReturnValueOnce()可能更容易。 - theberzi
2
但是这样你就无法对参数设置期望值了。 - André Willik Valenti
这是模拟。假设被模拟的事物不在测试范围内,否则就根本不应该进行模拟。 - theberzi
2
这是测试的一部分,以确保正确的参数被传递。 - André Willik Valenti
显示剩余2条评论

19

我以前认为我需要一种可以使用参数进行模拟的方法,但对我来说,只要知道调用顺序,就可以解决我的问题。

摘自Jest文档

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

所以在上面的例子中,只要你知道第一次运行该函数应返回true,第二次运行应返回false,你就可以愉快地使用它了!


14
我认为这种测试很脆弱,因为它依赖于非常特定的实现。如果在实现中调用的顺序并不重要,我不建议采用这种方法。 - Hanchen Jiang
2
不是的,但这个解决方案适用于需要关注调用顺序的情况。 - Luke Garrigan
1
好的。我只是认为对于OP的情况,调用顺序并不重要,所以我不会向OP建议这种解决方案。 - Hanchen Jiang
4
对于这个具体问题,没有相关答案。但是,如果你在谷歌上搜索“使用Jest模拟具有特定参数的函数”,会出现这个问题,并且我的回答只是简单地指出,你可能并不需要你认为需要的东西。 - Luke Garrigan
是的,这很公平。 - Hanchen Jiang

16
import * as helper from "../helper";  //file where all functions are

jest.spyOn(helper, "function_name").mockImplementation((argument) => {

// This argument is the one passed to the function you are mocking

  if (argument === "something") {
    return "something"
  } else {
    return "something else"
  }
});

12
目前 Jest 还没有这种功能,但是你可以使用 sinon 的 stubs 来实现。文档中的描述如下:

stub.withArgs(arg1[, arg2, ...]);

仅为提供的参数存根该方法。这对于在断言中更具表现力非常有用,因为您可以使用相同的调用访问 spy。它还可用于创建一个可以根据不同参数以不同方式响应的存根。

"test should stub method differently based on arguments": function () {
    var callback = sinon.stub();
    callback.withArgs(42).returns(1);
    callback.withArgs(1).throws("TypeError");

    callback(); // No return value, no exception
    callback(42); // Returns 1
    callback(1); // Throws TypeError
}

2

这可能会有所帮助...

我曾经遇到过类似的情况,即我使用相同的方法,并调用不同的参数,需要从桩/模拟调用中返回不同的结果。我使用了一个包含函数列表的变量,当我调用模拟服务时,我从队列的顶部取出函数并执行函数。它需要知道您正在测试的执行顺序,并不能真正处理通过参数改变响应,但已经让我绕过了jest中的限制。

var mockedQueue = [];
mockedQueue.push(() => {return 'A';})
mockedQueue.push(() => {return 'B';})

service.invoke = jest.fn(()=>{
    serviceFunctionToCall = mockedQueue.shift();
    return serviceFunctionToCall();
})

10
您可以多次使用mockReturnValueOnce而不是这样做。 - Black

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接