This issue facilitates discussions about builtin function mocks design. First of all, we believe that using mocks is so common that making them part of the library is a sane thing to do.
Sinon’s Spy
const spy = sinon.spy(() => 5)
// and for asserts with chai-sinon:
expect(mySpy).to.have.been.calledWith("foo");
What I don’t like about this design is that it doesn’t have support for easy creation of a spy that returns different values for different calls.
Another thing is that I always felt like there is way too many sinon-spy matchers but some really useful were missing.
Jest’s fn.mock()
const mock = jest.fn(x => 42 + x)
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
What I like about its design:
- clear design of
.mock
property exposing all the details - makes it possible to write custom expectations by hand,
- Simple helpers to mock different values for different calls:
mock.mockReturnValueOnce(‘x’)
Proposed design
In my experience, I quite often want to return different values for different calls, thus something like Jest’s mock.mockReturnValueOnce
(internal queue of returned values) is a must. But this leads us to this weird state where we define returned values in one place but write expectations in a totally another. What if we combined it to something like this:
import {mockFn} from "earl"
const getSizeMock = mockFn([{inputs: [expect.a(Fs), "/path/file.txt"], out: 217}])
This mock expects to be called exactly once with a object of type Fs
and string /path/file.txt
and returns 217
.
There are at least few cool things about this:
- can throw expectations as soon as unexpected call happens,
inputs
is just an array of args so one can use all matchers that would work with beEqual
(arrayContaining
if you don't care about details, or anything
if you really don't care ;d). This API design is just an example, we could utilize helpers like mockReturnValueOnce
etc but I wanted to present here that I believe that expectations on a mock can be defined as a part of a mock.
Furthermore, it should be possible to do something like expect(mock).toBeExhusted()
to verify if a mock's internal queue of values to return is empty. So there is no need for something like expect(mock).toHaveBeenCalledTimes(5)
.
I imagine that this could be even further simplified with optional integration with test runner (this needs another proposal) which would ensure all mocks created in a testcase are exhausted after each test run.
There is only one thing that I don't like about this idea - implementing autofix
(for inputs) will be challenging as expectations are defined first and execution happens later.
To clarify, this could be preferred way of using mocks but for sure there are cases where more traditional interface like:
const mock = jest.fn(x => 42 + x)
expect(mockFunc).toHaveBeenCalledTimes(3);
is needed and thus should be supported as well.
Thoughts?