Before we go into mocking, you must first accept that functions
are in fact a
special type of object. Since functions are actually objects, they also have
properties like a regular object.
const a = () => {a.count = (a.count || 0) + 1console.log(`count is currently ${a.count}`)}a() // will log 1a() // will log 2a() // will log 3a() // will log 4console.log(a.count) // will log 4// You can directly set properties to a functiona.count = 99a() // will log 100
A mock function is a fake function that doesn't do anything but keep track of
how many times it called. It also keeps track of the arguments that it is being
called with. You can create a mock function by calling jest.fn
function.
The mocked function contains an array of all the times the function has been called. You can access this array like this:
// a is a mock function.const a = jest.fn()a() // a is run without any arguments.console.log(a.mock.calls.length) // a.mock.calls is an array of length 1a('hello', 'world') // a is run with 2 arguments.console.log(a.mock.calls.length) // a.mock.calls is an array of length 2a('test', 1, 9, 100) // a is run with 4 arguments.console.log(a.mock.calls.length) // a.mock.calls is an array of length 3a(() => {console.log('hello')}) // a is run with 1 argumentconsole.log(a.mock.calls.length) // a.mock.calls is an array of length 4console.log(a.mock.calls)/*[[],['hello', 'world'],['test', 1, 9, 100],[ function ]]*/// a.mock.calls[0] is empty array because the first call has no arguments.// a.mock.calls[1] contains array of arguments passed into the function.// a.mock.calls[2] contains an array of arguments passed into the function.// a.mock.calls[3] contains an array of arguments passed into the function.a.mock.calls[3][0]() // prints out 'hello'
If you want a more complicated mock function that can return something, you can pass in a function. In the example below, nothing changes except your mocked function now returns a value! It can actually do something now.
const a = jest.fn((a, b) => {return a + b})console.log(a(6, 7)) // prints out 13console.log(a.mock.calls)/*[[6,7]]*/
We are testing a function that takes in a number and prints out "hello" that many times.
// solution.jsconst logHello = i => {if (i <= 0) returnconsole.log('hello')return logHello(i - 1)}module.exports = logHello
To test if the above function is working, I need to make sure that
logHello(-10)
- console.log
is called 0 times.logHello(0)
- console.log
is called 0 times.logHello(40)
- console.log
is called 40 times.I want to observe how many times console.log
is being called, so instead of
the original function, I should replace it with a mock function.
// solution.test.jsconst logHello = require('./solution.js')describe('logHello', () => {test('should log 0 times if -10 is passed in', () => {console.log = jest.fn()logHello(-10)expect(console.log.mock.calls.length).toEqual(0)})test('should log 0 times if 0 is passed in', () => {console.log = jest.fn()logHello(0)expect(console.log.mock.calls.length).toEqual(0)})test('should log hello 3 times if 3 is passed in', () => {console.log = jest.fn()logHello(3)expect(console.log.mock.calls.length).toEqual(3)// first argument in the first call should be hello.expect(console.log.mock.calls[0][0]).toEqual('hello')// first argument in the second call should be hello.expect(console.log.mock.calls[1][0]).toEqual('hello')// first argument in the third call should be hello.expect(console.log.mock.calls[2][0]).toEqual('hello')})})
Sometimes, the functions you are testing will depend on external modules that
you have no control over. To mock an entire module, you run
jest.mock('module-name')
In the example below, the function first sends a request to an API to get an array of objects. It will console.log the title for each title property of the object in the array.
// solution.jsconst request = require('request')const logTitles = () => {request('https://c0d3.com/api/lessons', (err, response, body) => {const arr = JSON.parse(body)arr.forEach(lesson => {console.log(lesson.title)})})}module.exports = logTitles
To test if the above function is working, I need to make sure that
request
to the API sends back an array string of 0 elements,
console.log
is called 0 times.request
to the API sends back an array string of 2 elements,
console.log
is called 2 times.Unlike the previous example, this one involves a library that we have no control
over, the request
library.
We need to overwrite the response so that we can simulate how many elements the API sends back.
Edit this page on Github// solution.test.jsjest.mock('request') // very first thing to do, mock the library// when this happens, this file will get the fake request libraryconst logTitles = require('./solution.js')// We need to change the mocked library so we can send back fake dataconst request = require('request')// solution.test.jsconst logHello = require('./solution.js')describe('logHello', () => {test('should call request 1 time', () => {request.mockClear() // we should always reset the mock of modulesconsole.log = jest.fn()logTitles()expect(request.mock.calls.length).toEqual(1)expect(console.log.mock.calls.length).toEqual(0)})test('should log 0 times if response is []', () => {request.mockClear() // we should always reset the mock of modules// If we don't reset the mock, it will include the calls from// the previous test.// we need to stringify it because request callback expects a stringconsole.log = jest.fn()const fakeResponse = JSON.stringify([])logTitles()// request is called 1 time with 2 arguments.// The second argument is a function. Let's run that functionrequest.mock.calls[0][1](null, null, fakeResponse)expect(console.log.mock.calls.length).toEqual(0)})test('should log 2 times if response has 2 objects', () => {request.mockClear() // we should always reset the mock of modules// we need to stringify it because request callback expects a stringconsole.log = jest.fn()const fakeResponse = JSON.stringify([{title: 'hello'},{title: 'world'}])logTitles()// request is called 1 time with 2 arguments.// The second argument is a function. Let's run that functionrequest.mock.calls[0][1](null, null, fakeResponse)expect(console.log.mock.calls.length).toEqual(2)expect(console.log.mock.calls[0][0]).toEqual('hello')expect(console.log.mock.calls[1][0]).toEqual('world')})})