reactjstypescriptjestjsreact-testing-libraryreact-testing

React testing `useEffect` with `useState` update


On my component render, my useEffects hooks called, a function. the function updates the state status depending on the condition within the useEffects produce.

So in this case how to test the `mobileMenu` and how to set different condition in useEffect to test it?

I hope both my useEffects and useState need to mocked. I am in learning process with react. I could not get any correct answer upon searching, any one help me please?

here is my app.tsx

my ts file:

import { Footer, Header, ProductCart, ProductPhotoGallery, Tabs } from '@mcdayen/components';
import { Cart, Logo, MobileMenu, NaviLinks, QuickSearch, User } from '@mcdayen/micro-components';
import { initialNaviLinksProps, initialPhotoProps, initialTabsProps, NaviLinksProps, sizeProps } from '@mcdayen/prop-types';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCartDetails, sizeHandler } from './store/cart.slice';
import { AppDispatch, RootState } from './store/store.config';

export function App() {
    const dispatch:AppDispatch = useDispatch();
    dispatch(fetchCartDetails());
    const {product} = useSelector((state:RootState) => state.cartStore)
    const [mobileMenu, setMobileMenu] = useState<boolean>(false);
    const [linkProps, setLinkProps] = useState<NaviLinksProps | null>(null);

    function mobileMenuHandler() {
        setMobileMenu((current: boolean) => !current);
    }

     useEffect(() => {
        setLinkProps(initialNaviLinksProps);
        const mobileId = document.getElementById('mobileMenu');
         if (mobileId?.offsetParent) {
            mobileMenuHandler();
        }
     }, []);
    
    useEffect(() => {
        setLinkProps((props) => {
            return mobileMenu ? { ...initialNaviLinksProps } : { ...initialNaviLinksProps, classProps: props?.classProps + ' hidden' }
        })
    }, [mobileMenu]);

    function onSizeSelect(selectedSize: sizeProps) {
        dispatch(sizeHandler(selectedSize));
    }
    
    return (
        <section  className="box-border m-auto flex flex-col pl-[18px] py-6   min-h-screen flex-wrap px-5 md:container md:w-[1440px] md:pl-[70px] pr-5 ">
            <Header>
                <Logo />
                {linkProps && <NaviLinks passNaviLinks={linkProps} />}
                <div className="flex gap-3">
                    <QuickSearch />
                    <Cart />
                    <User />
                    <MobileMenu menuHandler={mobileMenuHandler} />
                </div>
            </Header>
            <main  className='flex flex-col justify-between lg:flex-row'>
                <div className='hidden lg:block w-[325px]'>
                    <div>
                        <Tabs tabProps={initialTabsProps}  />
                    </div>
                </div>
                <div className='grow-0 flex-auto' >
                   {initialPhotoProps.length && <ProductPhotoGallery gallery={initialPhotoProps} />}
                </div>
                <div className='flex bg-white'>
                    {product && <ProductCart sizeSelect={onSizeSelect}  passCartProps={product} />}
                </div>
            </main>
            <Footer />
        </section>
    );
}

export default App;

My spec:


import { configureStore } from '@reduxjs/toolkit';
import { render } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import cartReducer from './store/cart.slice';

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useState: jest.fn(),
}));

export function createTestStore() {
  const store = configureStore({
        reducer: {
        cartStore:cartReducer,
        }
    })
  return store;
}

describe('App', () => {
     const setMobileMenu = jest.fn();
  const useStateMock = (initState: boolean) => [initState, setMobileMenu];

  jest.spyOn(React, 'useState').mockImplementation(useStateMock);

  afterEach(() => {
    jest.clearAllMocks();
  });
    const store = createTestStore();
    it('should render successfully', () => {
        const { baseElement } = render(
            <BrowserRouter>
                <Provider store={store}>{<App />}</Provider>
            </BrowserRouter>
        );
        expect(baseElement).toBeTruthy();
        useStateMock(true);
        expect(setMobileMenu).toHaveBeenCalledWith(true);
    });
});

I am getting an error at: `

jest.spyOn(React, 'useState').mockImplementation(useStateMock);

`

as : Argument of type '(initState: boolean) => (boolean | jest.Mock<any, any>)[]' is not assignable to parameter of type '() => [unknown, Dispatch<unknown>]'.

and my test failing.

Need help for:

  1. test the useEffect hook on anonymous function ( mocking )

  2. fixing the error highlighted

  3. testing the state on setMobileMenu

Any one please help me with the correct way?


Solution

  • Try to declare useStateMock as:

    const useStateMock = (initState: any) => [initState, setMobileMenu];