Julian
Julian

Reputation: 560

Jest Module Mocking in React

Getting into functional components + hooks and React Unit testing, I'm trying to do the following:

In one compnent, I use a function from a utility API file. On mount, Im making an async function call to my api. In that utility API file, I want to mock the function various ways so when I render my component in the test, it will see different responses in the hooks. It will return a Promise that will resolve to different values based on what I'm testing.

So I'm not mocking third party modules, Im mocking my own modules.

/**
 * App.js
 */
import React, { useState, useEffect } from 'react'
import { getData } from './api'

export default function App() {
  const [data, setData] = useState()

  // get data from api on mount
  useEffect(() => {
    loadData()
  }, [])

  // get the data and set the "data" state variable
  const loadData = async () => {
    try {
      let d = await getData()  // <---- this is what i want to mock with different values per test
      setData(d)
    } catch (err) {
      setErr('Error occurred')
    }
  }

  render (
    <div>
      data is: {JSON.stringify(data)}
    </div>
  )
}

Just looking for a way to define the mock in my test file per test, so when my component is rendered it will resolve/reject to different values

Upvotes: 0

Views: 2440

Answers (2)

You can mock your getData function from the api as follows:

import {render, wait} from 'react-testing-library';
import {getData} from 'path/to/api';

jest.mock('path/to/api', () => ({
    getData: jest.fn()
}))

it('succeeds when promise resolves', () => {
  // This overrides getData with a resolved promise
  getData.mockResolvedValue('foo');

  const { container } = render(<App />);
  // You need to wait for your mock promise to resolve
  return wait(() => {
    expect(container.textContent).toBe('data is: "foo"');
  });
});

it('fails when promise rejects', () => {
  // This overrides getData with a rejected promise
  getData.mockRejectedValue('oh no');

  const { container } = render(<App />);
  return wait(() => {
    expect(container.textContent).toBe('Error occurred');
  });
});

What this basically does is that when your api is imported it returns the dictionary returned by the mock factory.

You can mock the module with jest.mock, and then override the function to resolve/reject to different values in each test.

Upvotes: 3

helloitsjoe
helloitsjoe

Reputation: 6529

Daniel's answer explains a good method for mocking modules. Another way to mock getData is with dependency injection.

import {getData} from './api';

export default function App(props) {
  const [data, setData] = useState()

  // get data from api on mount
  useEffect(() => {
    loadData()
  }, [])

  // get the data and set the "data" state variable
  const loadData = async () => {
    try {
      let d = await props.getData() // <-- change this to props.getData()
      setData(d)
    } catch (err) {
      setErr('Error occurred')
    }
  }
}

App.defaultProps = { getData };

By setting the "real" getData as the default prop, your app will call the "real" getData from './api' when you render <App /> with no props.

However, in tests, you can now pass in a mock getData:

it('succeeds when promise resolves', () => {
  const mockGetData = jest.fn().mockResolvedValue('foo');

  const { container } = render(<App getData={mockGetData} />);
  // You need to wait for your mock promise to resolve
  return wait(() => {
    expect(container.textContent).toBe('data is: "foo"');
  });
});

it('fails when promise rejects', () => {
  const mockGetData = jest.fn().mockRejectedValue('oh no');

  const { container } = render(<App getData={mockGetData} />);
  return wait(() => {
    expect(container.textContent).toBe('Error occurred');
  });
});

Upvotes: 1

Related Questions