Search code examples
jestjsreact-hooks-testing-library

Testing custom hook with react-hooks-testing-library throws an error


I am trying to test a simple hook that fetches some data using axios. However the test is throwing a TypeError: "Cannot read property 'fetchCompanies' of undefined". Here's my custom hook (the full repo is here):

import { useState, useEffect } from 'react';
import { Company } from '../../models';
import { CompanyService } from '../../services';

export const useCompanyList = (): {
    loading: boolean;
    error: any;
    companies: Array<Company>;
} => {
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState();
    const [companies, setCompanies] = useState<Array<Company>>([]);

    useEffect(() => {
        const fetchData = async () => {
            try {
                setLoading(true);
                const companies = await CompanyService.fetchCompanies();

                // Sort by ticker
                companies.sort((a, b) => {
                    if (a.ticker < b.ticker) return -1;
                    if (a.ticker > b.ticker) return 1;
                    return 0;
                });
                setCompanies(companies);
                setLoading(false);
            } catch (e) {
                setError(e);
            }
        };

        fetchData();
    }, []);

    return { loading, error, companies };
};

and here's my test:

import { renderHook } from 'react-hooks-testing-library';
import { useCompanyList } from './useCompanyList';

const companiesSorted = [
    {
        ticker: 'AAPL',
        name: 'Apple Inc.'
    },
    ...
];

jest.mock('../../services/CompanyService', () => {
    const companiesUnsorted = [
        {
            ticker: 'MSFT',
            name: 'Microsoft Corporation'
        },
        ...
    ];

    return {
        fetchCompanies: () => companiesUnsorted
    };
});

describe('useCompanyList', () => {
    it('returns a sorted list of companies', () => {
        const { result } = renderHook(() => useCompanyList());

        expect(result.current.loading).toBe(true);
        expect(result.current.error).toBeUndefined();
        expect(result.current.companies).toEqual(companiesSorted);
    });
});

Please help me understand how to use react-hooks-testing-library in this case.

Edit

This seems to be related to a Jest issue that was seemingly resolved. Please see https://github.com/facebook/jest/pull/3209.


Solution

  • The

    TypeError: "Cannot read property 'fetchCompanies' of undefined"

    is caused by the way you define the CompanyService service. In the code, you are exporting an object CompanyService with all the service methods. But in your test, you are mocking the CompanyService to return an object with the methods.

    So, the mock should return a CompanyService object that is an object with all the methods:

    jest.mock('../../services/CompanyService', () => {
        const companiesUnsorted = [
            {
                ticker: 'MSFT',
                name: 'Microsoft Corporation'
            },
            ...
        ];
    
        return {
            CompanyService: {
                fetchCompanies: () => companiesUnsorted
            }
        };
    });
    

    Now, once you solve this, you will find that you don't have the TypeError anymore but your test is not passing. That is because the code you are trying to test is asynchronous, but your test is not. So, immediately after you render your hook (through renderHook) result.current.companies will be an empty array.

    You will have to wait for your promise to resolve. Fortunately, react-hooks-testing-library provides us a waitForNextUpdate function in order to wait for the next hook update. So, the final code for the test would look:

    it('returns a sorted list of companies', async () => {
        const { result, waitForNextUpdate } = renderHook(() => useCompanyList());
    
        expect(result.current.loading).toBe(true);
        expect(result.current.error).toBeUndefined();
        expect(result.current.companies).toEqual([]);
    
        await waitForNextUpdate();
    
        expect(result.current.loading).toBe(false);
        expect(result.current.error).toBeUndefined();
        expect(result.current.companies).toEqual(companiesSorted);
    });