Home > OS >  How to test react component with axios request in useEffect?
How to test react component with axios request in useEffect?

Time:12-23

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-libraryfor 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:

async-axios-react-testing-library

  • Related