I have react functional component with request in useEffect. https://codesandbox.io/s/nifty-dew-r2p1d?file=/src/App.tsx
const App = () => {
const [data, setData] = useState<IJoke | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
axios
.get("https://v2.jokeapi.dev/joke/Programming?type=single")
.then((res: AxiosResponse<IJoke>) => {
setData(res.data);
})
.catch((err) => console.log(err))
.finally(() => setIsLoading(false));
}, []);
return (
<div className="App">
{isLoading ? (
<h2>Loading...</h2>
) : (
<div className="info">
<div className="info__cat">
{data?.category ? `category: ${data.category}` : "bad category"}
</div>
<div className="info__joke">
{data?.joke ? `joke: ${data?.joke}` : "bad data"}
</div>
</div>
)}
</div>
);
};
How I can cover the component with tests? I need to test state before request, in time and after. How mock request in this context?
CodePudding user response:
Option 1. Use msw to mock by intercepting requests on the network level.
Option 2. If you don't want to install any package and setup, you can use jest.spyOn()
to create a mock resolved/rejected value for axios.get()
method.
The below example uses option 2.
App.tsx
:
import axios, { AxiosResponse } from 'axios';
import React, { useEffect, useState } from 'react';
interface IJoke {
category: string;
joke: string;
}
export const App = () => {
const [data, setData] = useState<IJoke | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
axios
.get('https://v2.jokeapi.dev/joke/Programming?type=single')
.then((res: AxiosResponse<IJoke>) => {
setData(res.data);
})
.catch((err) => console.log(err))
.finally(() => setIsLoading(false));
}, []);
return (
<div className="App">
{isLoading ? (
<h2>Loading...</h2>
) : (
<div className="info">
<div className="info__cat">{data?.category ? `category: ${data.category}` : 'bad category'}</div>
<div className="info__joke">{data?.joke ? `joke: ${data?.joke}` : 'bad data'}</div>
</div>
)}
</div>
);
};
App.test.tsx
:
import { App } from './App';
import axios, { AxiosResponse } from 'axios';
import { act, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
describe('70450576', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should render category and joke', async () => {
const mAxiosResponse = {
data: { category: 'smart', joke: 'sam' },
} as AxiosResponse;
jest.spyOn(axios, 'get').mockResolvedValueOnce(mAxiosResponse);
render(<App />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(await screen.findByText('category: smart')).toBeInTheDocument();
expect(await screen.findByText('joke: sam')).toBeInTheDocument();
});
});
Test result:
PASS examples/70450576/App.test.tsx (8.874 s)
70450576
✓ should render category and joke (43 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 91.67 | 72.22 | 80 | 90.91 |
App.tsx | 91.67 | 72.22 | 80 | 90.91 | 19
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.393 s, estimated 10 s
CodePudding user response:
You can't access to state
and setState
internally that with react-testing-library. This is because RTL wants you to test your component as a user would.
You should try to reproduce the set of actions that change the state.
Use render
function of react-testing-library
for rendering components, pass the mocked state
and setState
to this component.
Use axiosMock
from axios
to mock the axios.
In this approach you can check the mocked values as a real local states.
import { render } from "react-testing-library";
import axiosMock from "axios";
This link could be helpful: