Utility for creating mock implementations for Jest mocks.
First add jestor
as a devDependency.
npm install jestor --save-dev
or if you prefer Yarn:
yarn add jestor --dev
After that you can import jestor
in your tests and use it in any test file:
import { jestor } from 'jestor'
it('should return 4 whan called with 44', function () {
const mock = jest.fn()
// define mock implementation using jestor api
jestor(mock).whenCalledWith(44).return(4)
expect(mock(44)).toBe(4)
})
Proceed to API section to see what else jestor
can do for you.
jestor
function accepts Jest mock as a single argument and returns an object with the following methods: whenCalledWith
and followRules
for defining single and multiple rules correspondingly.
The best case for whenCalledWith
is when you want to define only one rule:
import { jestor } from 'jestor'
it('should return 4 whan called with 44', function () {
const mock = jest.fn()
// whenCalledWith allows to define only a single rule
jestor(mock).whenCalledWith(44).return(4)
expect(mock(44)).toBe(4)
})
If you want to define multiple rules, followRules
should be used.
It accepts a function, which define what mock should do when the given condition is met:
it('should allow to define multiple rules', function () {
const spy = jest.fn()
jestor(spy).followRules((rules) => {
rules.whenCalledWith(1).return(false)
rules.whenCalledWith(2).return(true)
rules.whenCalledWith('foo').resolveWith('foo')
rules.whenCalledWith('bar').rejectWith('bar')
})
expect(spy(1)).toBe(false)
expect(spy(2)).toBe(true)
const foo = await spy('foo')
expect(foo).toBe('foo')
await spy('bar').catch((e) => {
expect(e).toBe('bar')
})
})
As you've probably noticed, whenCalledWith
is used directly in case of single rule, and inside callback function in case of multiple rules.
In both cases whenCalledWith
is used for defining conditions for the arguments mock receives.
You can pass multiple arguments to whenCalledWith
:
import { jestor } from 'jestor'
it('should return 6 whan called with 2 and 3', function () {
const mock = jest.fn()
// whenCalledWith accepts multiple arguments
jestor(mock).whenCalledWith(2, 3).return(6)
expect(mock(2, 3)).toBe(6)
})
You can pass any asymmetrical Expect matcher to whenCalledWith
.
That includes matchers like anything
, any
, arrayContaining
, etc.
For example:
import { jestor } from 'jestor'
it('should return 6 whan called with string and 3', function () {
const mock = jest.fn()
// whenCalledWith also accepts expect matchers
jestor(mock).whenCalledWith(expect.any(String), 3).return(6)
expect(mock('foo', 3)).toBe(6)
expect(mock('baz', 3)).toBe(6)
})
The whenCalledWith
function returns an object which can be used to specify mock behavior - i.e. what should happen when mock is called with the given arguments.
For example, in jestor(mock).whenCalledWith(2, 3).return(6)
sentence the part .return(6)
describes mock behavior when it is called with arguments 2
and 3
.
You can use the following behaviors:
Describes that the value val
should be returned when mock is called:
import { jestor } from 'jestor'
it('should return 6 whan called with string and 3', function () {
const mock = jest.fn()
// tell jestor to return 6 when mock is called with 3
jestor(mock).whenCalledWith(3).return(6)
expect(mock(3)).toBe(6)
expect(mock(2)).not.toBe(6)
})
Describes that the the promise resolved with val
should be returned when mock is called:
import { jestor } from 'jestor'
it('should return resolved promise when resolveWith is used', function () {
const mock = jest.fn()
// tell jestor to return promise resolved with 6 when mock is called with 3
jestor(mock).whenCalledWith(3).resolveWith(6)
const promise = mock(3)
// assert that we got a promise
expect(promise.then).toBeDefined()
// when called with value other than 3, we'll not get a promise,
// as there is no jestor rule defined
expect(mock(2)).toBe(undefined)
// assert if we really got a promise resolved with 6
const result = await promise
expect(result).toBe(6)
})
Describes that the the promise rejected with val
should be returned when mock is called:
import { jestor } from 'jestor'
it('should return rejected promise when rejectWith is used', function () {
const mock = jest.fn()
// tell jestor to return rejected promise
jestor(mock).whenCalledWith(2).rejectWith('failure')
// assert if we really got a promise rejected with 'failure'
const err = await mock(2).catch((e) => e)
expect(err).toBe('failure')
})
Describes that value val
should be thrown when mock is called:
import { jestor } from 'jestor'
it('should throw an exception when throw is used', function () {
const mock = jest.fn()
// tell jestor to throw
jestor(mock).whenCalledWith(2).throw('exception')
// assert if we really got an exception
jestor(mock).whenCalledWith(2).throw('exception')
expect(() => {
spy(2)
}).toThrow('exception')
})
Let's imagine you have a mock, returned by jest.fn()
and want to define its implementation in a way that you specify what to return based on received arguments. Something like this:
describe('my function', function() {
it('should return a value', function() {
const mock = jest.fn()
mock.mockImplementation((obj1, obj2) => {
if (obj1 === 'Alice') {
return 'human'
}
if (typeof obj1 === 'number' && typeof obj2 === 'number') {
return 'numbers'
}
if (typeof obj1 === 'function') {
return 'function'
}
}))
})
})
Writing such mock implementation is unpleasant experience, you end up writing a bunch of if
statements and code quickly become messy.
Here is when jestor
comes to rescue. With jestor
test written above can be written as:
import { jestor } from 'jestor'
describe('my function', function () {
it('should return a value', function () {
const mock = jest.fn()
jestor(mock).followRules((rules) => {
rules.whenCalledWith('Alice').return('human')
rules.whenCalledWith(expect.any(Number), expect.any(Number)).return('numbers')
rules.whenCalledWith(expect.any(Function)).return('function')
})
})
})
It should have been named jester
, as jester is a person who jests.
However, the names jester
, jestr
and even jster
have been taken. As well as mocker
and jocker
.