1

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?

2 Answers 2

7

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(object, 'method').mockResolvedValueOnce() 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
Sign up to request clarification or add additional context in comments.

3 Comments

ok, I understand how to test this type of case, thanks)) But, what if we have custom request function, like here link? How to mock this func?
@СлаваИванов You import a function from a module, it's not an object, can't use jest.spyOn(object, 'method') anymore. Now, you could use jest.mock("./app.service") to mock the module. Jest will automatically hoist jest.mock calls to the top of the module (before any imports). The getJoke function is mocked, then you can mock the resolved/rejected value in each test case like getJoke.mockResolvedValueOnce(xxx).Besides, you could use the mocked helper function from ts-jest/utils to make the TS type correct.
+1 for using MSW. Don't mock implementation details of your request clients/functions. Approach API mocking on a higher level instead.
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.