Search code examples
javascriptreactjsunit-testingjestjsreact-context

How should I test for an API Call reject that is in a Context?


Before, I simply mocked the API response since it had a custom hook inside that delivered the data, the "Loading..." message and, in case of failure, an error message. I used the "mockRejectedValue" method to simulate said rejection:

Information Panel Component:

function AgentInformationPanel({ agentID, renderPanel }) {
    const { agent, loading, error } = useAgent(
        'https://valorant-api.com/v1/agents/',
        agentID
    );

    if (renderPanel)
        return (
          <div>
          {
            error && (
                    <div>
                        <p>Error: {error.message}</p>
                    </div>
            )
          }
          </div>

          // ...
        )

Test:

import React from 'react';
import { render, screen } from '@testing-library/react';
import AgentInformationPanel from './AgentInformationPanel.js';
import agentsMock from '../../../agentsMock/agentsMock.js';
import axios from 'axios';

const agent = agentsMock[0];
const agentID = agent.uuid;

jest.mock('axios');

beforeEach(() => {
    axios.get.mockResolvedValue({
        data: { data: agent },
        loading: true,
        error: 'Something went wrong D:'
    });
});

afterEach(() => {
    axios.mockRestore();
});

test('Should render "Error" message when API call fails', async () => {
    axios.get.mockRejectedValue(new Error('Something went wrong D:'));

    render(<AgentInformationPanel agentID={agentID} renderPanel={true} />);

    const errorElement = await screen.findByText(
        'Error: Something went wrong D:'
    );

    expect(errorElement).toBeInTheDocument();
});

// Other tests...

But NOW that I refactored my code, and was making use of Context API, I also refactored all the test file for this same component. I was able to mock the Context data and all but when I try to run the test for mock a rejected call from the API then I don't really know how to make it work :/

App.js

import React from 'react';
import Header from './components/Header/Header.js';
import AgentsContainer from './components/AgentsContainer/AgentsContainer.js';
import AgentInformationPanel from './components/AgentInformationPanel/AgentInformationPanel.js';
import { PanelContextProvider } from './contexts/Panel.context.js';
import './App.css';

function App() {
    return (
        <main>
            <Header />
            <PanelContextProvider>
                <AgentsContainer />
                <AgentInformationPanel />
            </PanelContextProvider>
        </main>
    );
}

export default App;

Panel Context:

import React, { useState, createContext } from 'react';
import { useAgent } from '../hooks/useAgent.js';

export const PanelContext = createContext();

export function PanelContextProvider({ children }) {
    const [id, setId] = useState();
    const [renderPanel, setRenderPanel] = useState(false);

    const { agent, loading, error } = useAgent(
        'https://valorant-api.com/v1/agents/',
        id
    );

    // This and the "restartDataForPanel" are just for a few buttons in the app.
    function setDataForPanel(agentId) {
        setId(agentId);
        setRenderPanel(true);
    }

    function restartDataForPanel() {
        setId();
        setRenderPanel(false);
    }

    return (
        <PanelContext.Provider
            value={{
                agent,
                loading,
                error,
                renderPanel
            }}>
            {children}
        </PanelContext.Provider>
    );
}

Information Panel Component (REF):

import { PanelContext } from '../../contexts/Panel.context.js';

function AgentInformationPanel() {
    const { agent, loading, error, renderPanel } = useContext(PanelContext);

    if (renderPanel)
        return (
          <div>
          {
            error && (
                    <div>
                        <p>Error: {error.message}</p>
                    </div>
            )
          }
          </div>

          // ...

Test (REF):

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import AgentInformationPanel from './AgentInformationPanel.js';
import agentsMock from '../../agentsMock/agentsMock.js';
import { PanelContext } from '../../contexts/Panel.context.js';

const agent = agentsMock[0];

// I used this object to run other tests, and it worked.
const Context = {
    agent: agent,
    loading: true,
    error: 'Something went wrong! D:',
    renderPanel: true
};

test('Should render "Error" message when API call fails', async () => {
    const rejectContext = {
        agent: null,
        loading: true,
        error: 'Something went wrong! D:',
        renderPanel: true
    };

    render(
        <PanelContext.Provider value={rejectContext}>
            <AgentInformationPanel />
        </PanelContext.Provider>
    );

    const errorElement = await screen.findByText(
        'Error: Something went wrong D:'
    );

    expect(errorElement).toBeInTheDocument();
});

// Othe tests...

Also, the "useAgent" custom hook is the same for both versions, but if you want to know how the "useAgent" hook is built:

useAgent Custom Hook:

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

export function useAgent(url, id) {
    const [agent, setAgent] = useState();
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState();

    useEffect(() => {
        setAgent();

        if (!id) return;

        setLoading(true);

        axios
            .get(url + id)

            .then(({ data: { data } }) => {
                setAgent(data);
            })

            .catch(error => setError(error))

            .finally(() => setLoading(false));
    }, [id]);

    return { agent, loading, error };
}

Solution

  • I already found the problem, it was the following:

    1. I was looking for "error.message", which didn't exist since I was just passing "error" with a string inside it, I had to pass it an "error" object with a property called "message" inside, which had to contain a string.
    2. As much as I added what was mentioned in the previous point, the test was looking for "Error: Something went wrong D:", when in fact it was missing an exclamation mark at the end of "... wrong!".