reactjsreact-hooksaxiosreact-testing-libraryreact-testing

How to mock axios call


I have a component Student component as follows

import axios from 'axios';
import React from 'react';
import { useEffect, useState } from 'react';

function Student(props) {
  const [studentRecord, setStudentRecord] = useState(props.stRecord);
  const [studentSubjects, setStudentSubjects] = useState(null);
  function getStudentSubjects() {
    let apicalladdress = '/studentapi/GetStudentSubjects/' + studentRecord.studentNumber;
    axios.get(apicalladdress).then((result) => {
      setStudentSubjects(result.data);
    });
  }
  useEffect(() => {
    getStudentSubjects();
  }, [studentRecord]);

  return (
    <div>
      <div>{studentRecord.studentNumber}</div>
      <div>{setStudentSubjects[0].subjectName}</div>
    </div>
  );
}

I have a test created as below

import {StudentSubjectsData} from "../globalDataProvider";
import AxiosMock from "axios"

it("make sure student renders",async ()=>{
  const mockStudentSubjects=await Promise.resolve({data: StudentSubjectsData()});
  AxiosMock.get.mockResolvedValue(mockStudentSubjects);
  
  render (<Student stRecord={StudentRecord}/>);
}

But I am getting following errors

Error 1. An update to Student component inside a test was not wrapped in act(...) for line :setStudentSubjects(result.data);

Error 2. For following print line, I am getting error TypeError: Cannot read properties of undefined (reading subjectName)

<div>{setStudentSubjects[0].subjectName}</div>

Any suggestions please...


Solution

  • I didn't see how you mock the axios.get() method, I will use jest.spyOn() to mock it.

    For the second error, the studentSubjects state is null for the first render. It's better to give an empty array instead of null as its initial value. Besides, you can use optional chain to access the value.

    Student.tsx:

    import axios from 'axios';
    import React from 'react';
    import { useEffect, useState } from 'react';
    
    export function Student(props) {
      const [studentRecord, setStudentRecord] = useState(props.stRecord);
      const [studentSubjects, setStudentSubjects] = useState<{ subjectName: string }[]>([]);
      function getStudentSubjects() {
        let apicalladdress = '/studentapi/GetStudentSubjects/' + studentRecord.studentNumber;
        axios.get(apicalladdress).then((result) => {
          setStudentSubjects(result.data);
        });
      }
      useEffect(() => {
        getStudentSubjects();
      }, [studentRecord]);
    
      return (
        <div>
          <div>{studentRecord.studentNumber}</div>
          <div>{studentSubjects[0]?.subjectName}</div>
        </div>
      );
    }
    

    Student.test.tsx:

    import { render, screen } from '@testing-library/react';
    import '@testing-library/jest-dom';
    import axios from 'axios';
    import React from 'react';
    import { Student } from './Student';
    
    describe('75502126', () => {
      test('should pass', async () => {
        const axioGetSpy = jest.spyOn(axios, 'get').mockResolvedValue({ data: [{ subjectName: 'a' }] });
        render(<Student stRecord={{ studentNumber: 1 }} />);
        expect(await screen.findByText('a')).toBeInTheDocument();
        axioGetSpy.mockRestore();
      });
    });
    

    Test result:

     PASS  stackoverflow/75502126/Student.test.tsx (8.98 s)
      75502126
        ✓ should pass (34 ms)
    
    -------------|---------|----------|---------|---------|-------------------
    File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -------------|---------|----------|---------|---------|-------------------
    All files    |     100 |      100 |     100 |     100 |                   
     Student.tsx |     100 |      100 |     100 |     100 |                   
    -------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        9.508 s