173

I want to mock a function with Jest, but only if it is called with specific arguments, for example:

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)

There is a similar feature implemented in Ruby's RSpec library:

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)

What I'm trying to achieve in my tests is a better decoupling, let's say I want to test a function that relies on 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

I know that there is a way to implement this by doing something like:

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");

But it would be nice if we had some sugar function to simplify it.

Does it sounds reasonable? Do we already have this feature in Jest?

Thanks for your feedback.

1
  • I've been dealing with this too, but in the end figured a very simple solution: const functionResult = myFunc(args); expect(functionResult).toBe('whatever'); Commented Aug 26, 2021 at 16:08

7 Answers 7

130

I found this library that a colleague of mine wrote recently: 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!');

Here's the library: https://github.com/timkindberg/jest-when

Sign up to request clarification or add additional context in comments.

Comments

85

jest-when mentioned by STeve Shary is probably the best option.

If you don't want to install a new library, when you don't care about the original function, here's a one-line solution:

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

7 Comments

Thanks for providing an alternative that doesn't require an external library.
If the order of the calls is known, it might be easier to instead chain .mockReturnValueOnce() multiple times.
But then you won't be able to set expectations on arguments
This is mocking. It's assumed that the thing being mocked is not under test, otherwise it shouldn't be mocked at all.
It is part of the test to ensure that correct arguments are passed
|
27
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"
  }
});

Comments

18

I thought I needed a way to mock with arguments too, but for me, I could solve my problem by simply knowing the order of calls.

Taken from the jest documentation

const filterTestFn = jest.fn();

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

So in the example above, provided you know that on first pass the function should return true and on second pass it should return false, you're good to go!

6 Comments

I think this kind of tests are fragile because it depends on a very specific implementation. If the order of the calls aren't important in the implementation, I would not suggest with this approach.
No, but this solution is for the scenario where the order of calls are important.
OK. I just don't think the order of calls are important for OP's case so I wouldn't suggest this solution to OP.
Not to the specific question no, but if you google "Can I mock functions with specific arguments using Jest" this question comes up and my answer is just merely pointing out that you may not need what you think you need.
Yeah that's fair.
|
13

No there is no way to do this in Jest yet. You could use sinons stubs for this. from the docs:

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

Stubs the method only for the provided arguments. This is useful to be more expressive in your assertions, where you can access the spy with the same call. It is also useful to create a stub that can act differently in response to different arguments.

"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
}

Comments

2

This may help...

I had something similar whereby I had the same method called with different parameters requiring different returned result from the stubbed/mocked call. I've used a variable with a list of functions when I've made a call to the mocked service i take function off the top of the queue and execute function. It requires knowledge of the order of execution you are testing and doesn't really handle varying the response by argument but has allowed me to work around the restriction in jest.

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

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

1 Comment

You can just use mockReturnValueOnce multiple times instead of this
1

You can do this in Jest, no external library required. Just keep a reference to the original implementation:

import * as MyModule from "./MyModule";

const originalMyFunction = MyModule.myFunction;

jest.spyOn(MyModule, "myFunction").mockImplementation((key) => {
 if (key === "keyIWantToMock") {
   return "someMockedValue";
 } else {
   return originalMyFunction(key);
 }
});

4 Comments

I've been trying this but I get RangeError: Maximum call stack size exceeded errors
Can you share your code @Yorkshireman?
Sorry Dan - I can't remember where the code was that I was working on with this - was a few weeks ago and it's been lost to the dusty drawers of my memory!
No worries :) my hunch is that if you were seeing maximum call stack size exceeded errors you were probably directly calling MyModule.myFunction inside the callback of mockImplementation, rather than calling a reference to the original value of MyModule.myFunction (originalMyFunction in my example)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.