2

I am using axios-mock-adapter to test my components. The strange thing is that only a single test can be run successfully because the next one always breaks. Each test works fine if run individually.

Here is my setup.

 "axios-mock-adapter": "^1.21.1",
 "axios": "^0.27.2",
 "@testing-library/jest-dom": "^5.16.1",
 "react": "^17.0.2",
 "typescript": "^4.5.4",

util.ts

export const client = axios.create({
    baseURL: process.env.REACT_APP_API_URL ? process.env.REACT_APP_API_URL : "",
    timeout: 10000,
});

The exported client is used internally by all my components

component.test.tsx

import MockAdapter from "axios-mock-adapter";
import {apiUrls, client} from "utils";
import {DUMMY_RESPONSE} from "dummy/data";
import {render, screen, waitFor} from "@testing-library/react";
import {MemoryRouter} from "react-router-dom";
import MyComponent from "MyComponent";

describe('MyComponent', () => {
    let mockClient: MockAdapter;
    beforeAll(() => {
        mockClient = new MockAdapter(client);
    })
    afterEach(() => {
        mockClient.reset()

    })
    afterAll(()=>{
        mockClient.restore()
    })

    it('should fetch images and render them', async () => {
        mockClient.onGet(apiUrls.myURL).reply(200, DUMMY_RESPONSE)
        const spy = jest.spyOn(client, 'get')
        render(
            <MemoryRouter>
                <MyComponent handleSubmit={() => {
                }}/>
            </MemoryRouter>)
        await waitFor(() => expect(spy).toBeCalledWith(apiUrls.fetchSecurityImages))
        await waitFor(() => expect(spy).toBeCalledTimes(1))
        await waitFor(() => {
            DUMMY_RESPONSE.forEach((image) => {
                expect(screen.getByAltText(image.alt)).toBeDefined()
            })
        })
        spy.mockClear()
    });
    it('should show loader while fetching images', async () => {
        mockClient.onGet(apiUrls.myURL).reply(200, DUMMY_RESPONSE)
        const spy = jest.spyOn(client, 'get')
        render(
            <MemoryRouter>
                <MyComponent handleSubmit={() => {
                }}/>
            </MemoryRouter>)
        
        await waitFor(() => expect(spy).toBeCalledWith(apiUrls.myURL))
        await waitFor(() => expect(spy).toBeCalledTimes(1))
        await waitFor(() => {
            DUMMY_RESPONSE.forEach((image) => {
                expect(screen.getByAltText(image.alt)).toBeDefined()
            })
        })
        spy.mockClear()
    });
});

The second test (I mean the second that is run not the order they are defined) always breaks because of an error in the useEffect() hook which throws:

Cannot read properties of undefined (reading 'data')
TypeError: Cannot read properties of undefined (reading 'data')

And this is the function that breaks:

useEffect(() => {
        const fetchImages = async () => {
            try {
                const response = await client.get(apiUrls.fetchImages) // <--- here we get undefined
                setImages(response.data)
            } catch (e: any) {
                handleErrors(e)
            }
        }
        fetchImages()
    }, [])

BTW I am aware those tests look the same. That is exactly the point. Earlier there was a delay introduced in the second one but while debugging I changed them to pinpoint the problem but with no luck.

CodeSamurai777
  • 3,285
  • 2
  • 24
  • 42

1 Answers1

0

your useEffect is triggered only once, since it has an empty array in the parameters. This is why only the first test is performed, when the second test is running, the function fetchImages() does not work.

You can see this for yourself if you add console.log to the code

useEffect(() => {
        const fetchImages = async () => {
            try {
                const response = await client.get(apiUrls.fetchImages)
                setImages(response.data)
            } catch (e: any) {
                handleErrors(e)
            }
        }
        fetchImages()
        console.log('render')
    }, [])

But I think that you are not just using useEffect in your code, most likely so that the request is sent only once, and not every re-render.

In the text you made a mistake when calling

mockClient.onGet(apiUrls.myURL).reply(200, DUMMY_RESPONSE)

At the same time , you expect

await waitFor(() => expect(spy).toBeCalledWith(apiUrls.fetchSecurityImages))

The data must match

I assume that you just made a typo and wanted to write such a test:

it('should fetch images and render them', async () => {
        mockClient.onGet(apiUrls.fetchSecurityImages).reply(200, DUMMY_RESPONSE)
        const spy = jest.spyOn(client, 'get')
        render(
            <MemoryRouter>
                <MyComponent handleSubmit={() => {
                }}/>
            </MemoryRouter>)
        await waitFor(() => expect(spy).toBeCalledWith(apiUrls.fetchSecurityImages))
        await waitFor(() => expect(spy).toBeCalledTimes(1))
        await waitFor(() => {
            DUMMY_RESPONSE.forEach((image) => {
                expect(screen.getByAltText(image.alt)).toBeDefined()
            })
        })
        spy.mockClear()
    });
BuGaGa
  • 302
  • 2
  • 9