1

I have this component:

export const CityForm: FC = () => {
    const { state: widgetState } = useContext(WidgetContext);
    const { dispatch: geocodingDispatch } = useContext(GeocodingContext);

    const [city, setCity] = useState<string>('');
    const debouncedCity = useDebouncedValue<string>(city, 800);

    const fetchCallback = useCallback(() => {
        getLocationCoords(geocodingDispatch)(widgetState.appid, debouncedCity);
    }, [debouncedCity]);

    useEffect(() => {
        if (debouncedCity && widgetState.appid) {
            fetchCallback();
        } else {
            clearLocationCoords(geocodingDispatch)();
        }
        return () => {
            clearLocationCoords(geocodingDispatch)();
        }
    }, [debouncedCity]);

    const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
        setCity(e.target.value);
    };

    return (
        <Input
            data-testid={TEST_ID.CITY_FORM}
            type={'text'}
            value={city}
            onChange={handleOnChange}
            placeholder={"Type your City's name..."}
        />
    );
};

I'm trying to write a test to check if getLocationCoords function will be called after user input.

this is the test:

    it('should call getLocationCoords action after 800 miliseconds', async () => {
        const dispatch = jest.fn(() => null);
        const testCity = 'city';
        const getLocationCoords = jest.fn(() => null);

        jest.spyOn(useDebouncedValueModule, 'useDebouncedValue')
            .mockImplementation((value) => value);

        const component = render(
            <ThemeProvider theme={lightTheme}>
                <WidgetContext.Provider value={{ state: { appid: 'test' } as IWidgetState, dispatch: dispatch }}>
                    <GeocodingContext.Provider value={{ state: {} as IGeocodingState, dispatch: dispatch }}>
                        <CityForm />
                    </GeocodingContext.Provider>
                </WidgetContext.Provider>
            </ThemeProvider >
        );

        const input = await component.findByTestId(TEST_ID.CITY_FORM);

        expect(input).toHaveValue('');

        await userEvent.type(input, testCity);

        expect(input).toHaveValue(testCity);

        expect(getLocationCoords).toHaveBeenCalledTimes(1);
    });

expect(getLocationCoords).toHaveBeenCalledTimes(1); Received number of calls: 0.

I cannot figure out why this happens. As the component works properly during manual testing.

Since the useDebouncedValue is mocked and returns the value instantaneously the debouncedCity within the component should update and trigger the useEffect

1 Answers1

0

I had to add a spy on getLocationCoords like so:

    it('should call getLocationCoords action with testCity as param after after input', async () => {
        jest.spyOn(useDebouncedValueModule, 'useDebouncedValue')
            .mockImplementation((value) => value);

        const getLocationCoordsSpy = jest.spyOn(geocodingActions, 'getLocationCoords');

        const component = render(
            <ThemeProvider theme={lightTheme}>
                <WidgetContext.Provider value={{ state: { appid: 'test' } as IWidgetState, dispatch: dispatch }}>
                    <GeocodingContext.Provider value={{ state: {} as IGeocodingState, dispatch }}>
                        <CityForm />
                    </GeocodingContext.Provider>
                </WidgetContext.Provider>
            </ThemeProvider >
        );

        const input = await component.findByTestId(TEST_ID.CITY_FORM);

        expect(input).toHaveValue('');

        act(() => {
            fireEvent.change(input, { target: { value: testCity } });
        });

        expect(input).toHaveValue(testCity);

        expect(getLocationCoordsSpy).toHaveBeenCalledTimes(1);