In this third part we are going to talk about returning values from mocks. jest.mock()
is a method that mocks a module. It takes the module and replaces it with a mocking function, jest.fn()
. When we talk about returning values from a mock, we mean that we return values from a mocking function, jest.fn()
. We already saw how to return values from mocks in part 1.
// 1. call jest.fn() with a mock implementation parameter
jest.fn(() => 'Hello')
jest.fn((value) => 'Hello ' + value)
// 2. use jest.fn() methods
jest.fn().mockReturnValue('Hello')
jest.fn().mockImplementation((value) => 'Hello ' + value)
But, how does this work for mocking modules with jest.mock()
? We will look at using:
- The methods of
jest.fn()
- Passing the mock implementation parameter in
jest.fn()
. - Using manual mocks.
The examples I use in this article are available on github (src/part3). These files a build upon create-react-app so you can run them using npm run start
or run the tests using npm run test
.
1. Using mocking function (jest.fn) methods
We are going to start of with the easiest one, using the methods Jest
provides us on mocking functions. To illustrate this, we will be using a new example using the children props pattern:
// part3/example1/WrapperComponent.js
function WrapperComponent(props){
return(
<div className="WrapperComponent">
<div>Wrapper Component</div>
{props.children}
</div>
)
}
export default WrapperComponent
// part3/example1/ParentComponent.js
import WrapperComponent from './WrapperComponent'
function ParentComponent(){
return(
<div className="ParentComponent">
<div>Parent Component</div>
<WrapperComponent>
<div>Textblock 1.</div>
<div>Textblock 2.</div>
</WrapperComponent>
<WrapperComponent>
<div>Textblock 3.</div>
<div>Textblock 4.</div>
</WrapperComponent>
</div>
)
}
export default ParentComponent
Take a close look at these components and think how you would test the parent. Let us try to use .toHaveBeenCalledWith
on an automatic mock:
// part3/example1/__tests__/test1.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent from '../WrapperComponent'
jest.mock('../WrapperComponent')
test('ParentComponent renders correctly', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
})
// fails cause [] euhhhh
test('Wrapper mock was called with the correct arguments', () => {
render(<ParentComponent />)
expect(WrapperComponent).toHaveBeenCalledTimes(2)
expect(WrapperComponent).toHaveBeenNthCalledWith(1,
expect.objectContaining({
children: [] // euh
}),
expect.anything()
)
})
We run into a problem when we try to test the children prop of the wrapper components. How do we describe these children? We can actually do it:
<div>Textblock 1.</div>
<div>Textblock 2.</div>
becomes
expect.objectContaining({
children: [<div>Textblock 1</div>, <div>Textblock 2</div>]
}),
But it is messy. Also remember that we are using simple examples. What would you do when there is more text or even another component in there? How would we test this then?
The solution is to return the children from the mock. The basic function of the wrapper is to receive something (children), wrap this something in some html and then return that:
// component receives
prop.children
// component returns
<somehtml>props.children</somehtml>
The plan is to mock the wrapper and then just return the children from this mock:
// mock receives (get called with)
prop.children
// mock returns
props.children
The mock passes on what it receives without any alterations. When rendering the parent this would happen:
Test receives:
<WrapperComponent>
<div>Textblock 1.</div>
<div>Textblock 2.</div>
</WrapperComponent>
The test renders:
<div>Textblock 1.</div>
<div>Textblock 2.</div>
And this we can test:
expect(screen.getByTest(/Texblock 1/)).toBeInTheDocument()
expect(screen.getByTest(/Texblock 2/)).toBeInTheDocument()
What would our mock look like?
// mock the module
jest.mock('../WrapperComponent')
// add return value
WrapperComponent.mockImplementation(
props => <>{props.children}</>
)
First, we mock the module, then we use the Jest
method .mockImplementation()
to add a return value to the WrapperComponent
mock. What is this implementation? We just return the children: props => <>{props.children}</>
.
Let's go over this again. We establised that it was too complex to map out the children for testing in a expect.objectContaining()
statement. So we needed another method.
The wrapper takes the children and then returns them wrapped in some html. So, if we let our mock return the children without altering or wrapping them, then:
- Our mock doesn't to anything anymore which is great because it won't pollute the parent test.
- We still have access to our children, because they get rendered which is great because this means we can test them.
To make our mock return the children, we used the method .mockImplementation()
.
The full test here:
// part3/example1/__tests__/test2.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent from '../WrapperComponent'
jest.mock('../WrapperComponent')
beforeEach(() => {
// eslint-disable-next-line testing-library/no-node-access
WrapperComponent.mockImplementation(props => <>{props.children}</>)
})
test('ParentComponent renders correctly', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
})
// you wouldn't write this test in a real app, but it makes sense here
test('WrapperComponent is mocked and not rendered', () => {
render(<ParentComponent />)
expect(screen.queryAllByText(/Wrapper Component/i)).toHaveLength(0)
})
test('Wrapper mock was called correctly', () => {
jest.clearAllMocks()
render(<ParentComponent />)
expect(WrapperComponent).toHaveBeenCalledTimes(2)
})
test('Wrapper children got rendered', () => {
render(<ParentComponent />)
expect(screen.getAllByText(/Textblock/)).toHaveLength(4)
})
This should all make sense now. A couple of remarks:
-
Eslint
doesn't like it when you call props.children directly insideJest
: "Avoid direct node access". But I wouldn't know how to test this without calling props.children, so I added aneslint-disable-next-line
rule. -
These 2 lines are equivalent:
WrapperComponent.mockImplementation(props => <>{props.children}</>) WrapperComponent.mockImplementation(props => props.children)
I prefer the first because it makes it more clear that you are returing
jsx
. You can use either at your own discretion.
This concludes our first method of returning a value from a mock, using methods on mocking functions like .mockImplementation()
.
2. Passing a mock implementation parameter into the jest.fn()
It's a bit of a mouthfull but basically just looks like this:
jest.fn((value) => 'Hello ' + value)
We return a value by simply passing in an implementation (a function). This function we pass in, replaces the component we are mocking.
But, where is the jest.fn()
when we use automatic mocking?
jest.mock('path')
Jest
has hidden it from us and we can't access it. How then do we do this? We will manually write the mocking function for jest.mock
. (Carefull, this is something different from manual mocking which we will see later.)
Up until now, we've been using automatic mocks. Calling jest.mock(path)
sets up a mock for us. But, jest.mock()
can take a second parameter: jest.mock(path, moduleFactory)
. Jest
calls this the module factory parameter:
jest.mock(path, moduleFactory)
takes a module factory argument. A module factory is a function that returns the mock.
So we need to write a moduleFactory: a function that returns a mock. Reusing the previous example, it looks like this:
jest.mock('../WrapperComponent', () => {
return jest.fn()
})
// alternative syntax
jest.mock('../WrapperComponent', () => jest.fn())
I prefer the first version with the explicit return as I find it more readable. But it is your choice.
Why do we use this moduleFactory? Because we needed access to jest.fn()
. Why do we need access to jest.fn()
? Because we want to pass a mock implemetation parameter into it. What would this implementation be? The same thing we returned in our previous example:
// previous
WrapperComponent.mockImplementation(props => <>{props.children}</>)
// now
jest.fn((props) => <>{props.children}</>)
And we're done. Here is the full test. It's the same as the previous example except how we return a value from jest.mock
.
// part3/example1/__test__/test3.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent from '../WrapperComponent'
jest.mock('../WrapperComponent', () => {
// eslint-disable-next-line testing-library/no-node-access
return jest.fn((props) => <>{props.children}</>)
})
test('ParentComponent renders correctly', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
})
// you wouldn't write this test in a real app, but it makes sense here
test('WrapperComponent is mocked and not rendered', () => {
render(<ParentComponent />)
expect(screen.queryAllByText(/Wrapper Component/i)).toHaveLength(0)
})
test('Wrapper mock was called correctly', () => {
jest.clearAllMocks()
render(<ParentComponent />)
expect(WrapperComponent).toHaveBeenCalledTimes(2)
})
test('Wrapper children got rendered', () => {
render(<ParentComponent />)
expect(screen.getAllByText(/Textblock/)).toHaveLength(4)
})
Let me go over the mock once again:
// theory
jest.mock(path, moduleFactory)
jest.fn(implementation)
// practice
jest.mock('../WrapperComponent', () => {
return jest.fn((props) => <>{props.children}</>)
})
We pass a second argument to jest.mock()
called the module factory. This is just a function that returns a mocking function.
Inside our jest.fn()
, we pass a mock implementation parameter. This is where we add a return value to the mock.
Remarks:
- Again eslint yells at us from using props.children.
-
jest.mock
gets called at the root level of a file. We set up a return value from the mock at the root. So every test render will get this return. If you need different behaviour, turn to.mockImplementation
or.mockImplementationOnce
.
Intermediate section: default and named imports
There is another issue with using the moduleFactory parameter. Until now, we've always worked with default exported modules. But what to do with named exports? We make a small adjustment to the previous example:
// part3/example2/WrapperComponent.js
function WrapperComponent(props){
return(
<div className="WrapperComponent">
<div>Wrapper Component</div>
{props.children}
</div>
)
}
function ExtraComponent(){
return(
<div className="ExtraComponent">
Extra Component
</div>
)
}
export default WrapperComponent
export { ExtraComponent }
// part3/example2/ParentComponent
import WrapperComponent, { ExtraComponent } from './WrapperComponent'
function ParentComponent(){
return(
<div className="ParentComponent">
<div>Parent Component</div>
<WrapperComponent>
<div>Textblock 1</div>
<div>Textblock 2</div>
</WrapperComponent>
<ExtraComponent />
</div>
)
}
export default ParentComponent
We added a component ExtraComponent
to the WrapperComponent.js file. The extra component is a named export. In the parent we just call the extra component. Let's now show the difference in mocking named versus default exports.
Default export only
We already saw how to mock a default export:
jest.mock('../WrapperComponent', () => {
return jest.fn(props => <>{props.children}</>)
})
Note that this will not mock ExtraComponent
. I am just showing what to do when mocking a default export.
Named export only
jest.mock('../WrapperComponent', () => {
return {
ExtraComponent: jest.fn()
}
})
So, jest.mock()
takes the path and then the moduleFactory function. That is still the same. What is new is that moduleFactory now returns an object. This object has the named export ExtraComponent
as a property with jest.fn()
as it's value. That is how you mock the named export. (This does not mock the default export!)
Named + default export
jest.mock('../WrapperComponent', () => {
return{
__esModule: true,
default: jest.fn((props) => <>{props.children}</>),
ExtraComponent: jest.fn()
}
})
First argument is the path, second is a moduleFactory function that returns an object. The ExtraComponent property also remained the same. The default prop on our object corresponds to export default
. So default corresponds to the WrapperComponent
. What do we return for the wrapper? A mocking function with props.children as return value.
The final property __esModule
(mind the double underscore) is required to make the default prop work: Jest
documentation.
Here is the final test:
// part3/example2/__tests__/test1.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent, { ExtraComponent } from '../WrapperComponent'
beforeEach(() => {
jest.clearAllMocks()
})
jest.mock('../WrapperComponent', () => ({
__esModule: true,
// eslint-disable-next-line testing-library/no-node-access
default: jest.fn((props) => <>{props.children}</>),
ExtraComponent: jest.fn()
}))
test('ParentComponent renders correctly', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
})
test('WrapperComponent was mocked correctly', () => {
render(<ParentComponent />)
expect(WrapperComponent).toHaveBeenCalledTimes(1)
})
test('WrapperComponent children rendered correctly', () => {
render(<ParentComponent />)
expect(screen.getAllByText(/TextBlock/i)).toHaveLength(2)
})
test('ExtraComponent was mocked correctly', () => {
render(<ParentComponent />)
expect(ExtraComponent).toHaveBeenCalledTimes(1)
})
A final sidenote: automatic mocks work just fine on default and named exports. Jest
is pretty clever. Of course this will no longer return any values from the mocks.
// part3/example2/__tests__/test2.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent, { ExtraComponent } from '../WrapperComponent'
jest.mock('../WrapperComponent')
test('Auto mock worked', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
// mocking default export works
expect(WrapperComponent).toHaveBeenCalledTimes(1)
// mocking named export works
expect(ExtraComponent).toHaveBeenCalledTimes(1)
})
3. Manual mocks
Thusfar we've looked at 2 methods to add a return value to a mock. Using the methods on jest.fn()
and passing a mock implementation function into jest.fn()
. The third method is what Jest
calls manual mocks.
We will continue working with our previous example. So, we have a parent component and then a file with a wrapper and an extra component. To create a manual mock for this file WrapperComponent.js we add a folder "__mocks__" at the same level of the file. Inside this map, we add a new file WrapperComponent.js:
// part3/example3/__mocks__/WrapperComponent.js
const WrapperComponent = jest.fn().mockImplementation((props) => <>{props.children}</>)
const ExtraComponent = jest.fn()
export default WrapperComponent
export { ExtraComponent }
We return mocking functions for each component. For the wrapper component we added the return value to the mock. Next, in our test we just add an automatic mock:
jest.fn('../WrapperComponent')
When we render the test, Jest
will check if there's a manual mock in the reserved folder and use that. And that's all. We created a manual mock. The test:
// part3/example3/__tests__/test1.js
import { screen, render } from '@testing-library/react'
import ParentComponent from '../ParentComponent'
import WrapperComponent, { ExtraComponent } from '../WrapperComponent'
beforeEach(() => {
jest.clearAllMocks()
})
jest.mock('../WrapperComponent')
test('ParentComponent renders correctly', () => {
render(<ParentComponent />)
expect(screen.getByText(/Parent Component/i)).toBeInTheDocument()
})
test('WrapperComponent was mocked correctly', () => {
render(<ParentComponent />)
expect(WrapperComponent).toHaveBeenCalledTimes(1)
})
test('WrapperComponent children rendered correctly', () => {
render(<ParentComponent />)
expect(screen.getAllByText(/TextBlock/i)).toHaveLength(2)
})
test('ExtraComponent was mocked correctly', () => {
render(<ParentComponent />)
expect(ExtraComponent).toHaveBeenCalledTimes(1)
})
This is a simple example to show how manual mocks work. In a real testing environment, manual mocks are used to mock complex components, especially components that fetch and then return data.
Intermediate section: moking external modules (node-packages)
On a sidenote: mocking node-packages works exactly the same as mocking internal modules. You can either use an automatic mock:
jest.mock('package-name')
Or use the moduleFactory parameter:
jest.mock('package-name', () => {
__esModule: true,
default: jest.fn(),
'someFunction': jest.fn()
})
Returning values from mocked node modules is also identical.
Summary
In this article, I showed how to return data from mocks:
- Using
jest.fn()
methods. - Using
jest.fn()
with a mock implementation. - Using manual mocks.
We tested a React
component with children props to illustrate this and we also took a look at the difference between mocking a named versus mocking a default export.
In the next and last part of this series we will look at some caveats of using mocks in Jest
tests and we will wrap up this series with some more examples of testing React
coding patterns.
Top comments (0)