Returning values from mocks (Jest mocking + React part 3)

Peter Jacxsens - Sep 8 '22 - - Dev Community

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)
Enter fullscreen mode Exit fullscreen mode

But, how does this work for mocking modules with jest.mock()? We will look at using:

  1. The methods of jest.fn()
  2. Passing the mock implementation parameter in jest.fn().
  3. 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
Enter fullscreen mode Exit fullscreen mode
// 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
Enter fullscreen mode Exit fullscreen mode

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()
  )
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

becomes

expect.objectContaining({
  children: [<div>Textblock 1</div>, <div>Textblock 2</div>]
}),
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

The test renders:

  <div>Textblock 1.</div>
  <div>Textblock 2.</div>
Enter fullscreen mode Exit fullscreen mode

And this we can test:

expect(screen.getByTest(/Texblock 1/)).toBeInTheDocument()
expect(screen.getByTest(/Texblock 2/)).toBeInTheDocument()
Enter fullscreen mode Exit fullscreen mode

What would our mock look like?

// mock the module
jest.mock('../WrapperComponent')
// add return value
WrapperComponent.mockImplementation(
  props => <>{props.children}</>
)
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

This should all make sense now. A couple of remarks:

  1. Eslint doesn't like it when you call props.children directly inside Jest: "Avoid direct node access". But I wouldn't know how to test this without calling props.children, so I added an eslint-disable-next-line rule.
  2. 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)
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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}</>)
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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}</>)
})
Enter fullscreen mode Exit fullscreen mode

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:

  1. Again eslint yells at us from using props.children.
  2. 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 }
Enter fullscreen mode Exit fullscreen mode
// 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
Enter fullscreen mode Exit fullscreen mode

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}</>)
})
Enter fullscreen mode Exit fullscreen mode

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()
  }
})
Enter fullscreen mode Exit fullscreen mode

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()
  }
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

Or use the moduleFactory parameter:

jest.mock('package-name', () => {
  __esModule: true,
  default: jest.fn(),
  'someFunction': jest.fn()
})
Enter fullscreen mode Exit fullscreen mode

Returning values from mocked node modules is also identical.


Summary

In this article, I showed how to return data from mocks:

  1. Using jest.fn() methods.
  2. Using jest.fn() with a mock implementation.
  3. 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player