Reputation: 560
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
Reputation: 392
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
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