2

I am trying to test a functional component which is below :

export const MyFunctionalComponent=()=>{ 

const {id, otherid} = useSelector((state) => state.user);
    const accounts = useSelector((state) => state.Selection.accounts);

    const dispatch = useDispatch();

    useEffect(() => {
        if (!accounts) {
            dispatch(getDetails(id, otherid));
        }
    }, []);

    const handleOnClick = (Id) => {
        dispatch(saveId(Id));
    };

    if (!accounts) {
        return <CircularLoader />;
    }

    if (!accounts.length) {
        return (
            <AccessUnavailablePanel
               
            />
        );
    }

    if (accounts.length === 1) {
        return <Redirect to={`${accounts[0].id}`} />;
    }

    return (
        <MyList
            accounts={accounts}
            handleOnClick={handleOnClick}
        />
    );
};

i am new to JEST , how can i correctly mock useSelector, useDispatch and useEffect ?

i am trying to test as follows :


jest.mock('react-redux', () => ({
    ...jest.requireActual('react-redux'),
    useSelector: jest.fn(),
    useDispatch: jest.fn()
}));

describe('<MyFunctionalComponent/>', () => {
    const useEffect=jest.spyOn(React, "useEffect").mockImplementation(() => {})
    const useSelectorMock = reactRedux.useSelector;
    const useDispatchMock = reactRedux.useDispatch;
    const user = {
        id: '32sdfsdfr-fjdfdk33-4434sdfs',
        otherid: '73587hdfjg-dfghd94-jgdj'
    };
    const store = mockStore({
        user:user,
        Selection: {
            Id: '',
            accounts: null
        }
    });

    let wrapper;
    const setup = () => {
        const {result} = renderHook(() => MyFunctionalComponent(), {
            wrapper: ({children}) => (
                <Provider store={store}>{children}</Provider>
            )
        });
        return result;
    };

    describe('Rendering accounts available', () => {
        beforeEach(() => {
            useDispatchMock.mockImplementation(() => () => {});
            useSelectorMock.mockImplementation((selector) =>
                selector(mockStore)
            );
        });
        afterEach(() => {
            jest.clearAllMocks();
            useSelectorMock.mockClear();
            useDispatchMock.mockClear();
        });
        it('should render the accounts if more than 1 account available', () => {
            useSelectorMock.mockReturnValue(user);
            const mockdata = {
                accounts: [
                    {
                       id: '637ghgh',
                       
                    },
                    {
                        id: '10a190abd',
                       
                    }
                ]
            };
            const getDetails = jest.spyOn(
                SelectionActions,
                'getDetails'
            );
            useSelectorMock.mockReturnValue(mocdata);
            useDispatchMock.mockReturnValue(
                getDetails(user.id, user.otherid)
            );
            wrapper = setup();
            store.dispatch(getDetails(user.id, user.otherid))
            sleep(500);
            const actions=store.getActions();
            console.debug(actions[0])
            expect(getDetails).toHaveBeenCalledWith(user.id, user.otherid);
            expect(actions[0].type).toEqual('GET_DETAILS')
            expect(wrapper.current).toMatchSnapshot();
        });
});

But my snapshot seems to return AccessUnavialable, and the payload is undefined. How can I modify my tests to correctly return the mocked data as payload and get the correct snapshot?

2
  • I wouldn't mock useEffect neither useDispatch since you've wrapped the component in a Provider. Also, shouldn't mockdata be an array? Commented Sep 1, 2021 at 12:38
  • @alextrastero It was foolish of me to define mock as object. It worked when i changed it to array. Thank you. Commented Sep 2, 2021 at 5:59

1 Answer 1

1

It can be tested by mocking libraries such as react-redux, but it is not recommended. It pays too much attention to implementation details. A better way is to use the mock store to provide only mock data instead of mock implementation details. Use the original, unmocked useSelector and useEffect hooks, which are closer to the real function of the code, rather than the implementation details of the mock.

In addition, creating mock objects and their implementation are also very cumbersome steps. You should also pay attention to cleaning up mock objects to avoid being used by other test cases, resulting in test failures.

Use redux-mock-store to create a mock store with the mocked state.

Each test case provides different mock data to drive component rendering, and finally, assert whether the component rendering is correct.

For simplicity, just to demonstrate this test method, I replaced your component with a simple component, the principle is the same.

E.g.

index.tsx:

import { useSelector, useDispatch } from 'react-redux';
import { useEffect } from 'react';
import React from 'react';

const getDetails = (id, otherId) => ({ type: 'GET_DETAILS' });

export const MyFunctionalComponent = () => {
  const { id, otherid } = useSelector((state: any) => state.user);
  const accounts = useSelector((state: any) => state.Selection.accounts);

  const dispatch = useDispatch();

  useEffect(() => {
    if (!accounts) {
      dispatch(getDetails(id, otherid));
    }
  }, []);

  if (!accounts) {
    return <div>CircularLoader</div>;
  }

  if (!accounts.length) {
    return <div>AccessUnavailablePanel</div>;
  }

  if (accounts.length === 1) {
    return <div>Redirect</div>;
  }

  return <div>MyList</div>;
};

index.test.tsx:

import { MyFunctionalComponent } from './';
import createMockStore from 'redux-mock-store';
import { screen, render } from '@testing-library/react';
import { Provider } from 'react-redux';
import React from 'react';

const mockStore = createMockStore([]);

describe('69013562', () => {
  test('should render AccessUnavailablePanel', () => {
    const state = {
      user: { id: '1', otherid: '2' },
      Selection: { accounts: [] },
    };
    const store = mockStore(state);
    render(<MyFunctionalComponent />, {
      wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
    });
    expect(screen.getAllByText('AccessUnavailablePanel')).toBeTruthy();
    expect(store.getActions()).toEqual([]);
  });

  test('should render CircularLoader and dispatch get details action', () => {
    const state = {
      user: { id: '1', otherid: '2' },
      Selection: {},
    };
    const store = mockStore(state);
    render(<MyFunctionalComponent />, {
      wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
    });
    expect(screen.getAllByText('CircularLoader')).toBeTruthy();
    expect(store.getActions()).toEqual([{ type: 'GET_DETAILS' }]);
  });

  test('should render Redirect', () => {
    const state = {
      user: { id: '1', otherid: '2' },
      Selection: { accounts: [1] },
    };
    const store = mockStore(state);
    render(<MyFunctionalComponent />, {
      wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
    });
    expect(screen.getAllByText('Redirect')).toBeTruthy();
    expect(store.getActions()).toEqual([]);
  });

  test('should render MyList', () => {
    const state = {
      user: { id: '1', otherid: '2' },
      Selection: { accounts: [1, 2, 3] },
    };
    const store = mockStore(state);
    render(<MyFunctionalComponent />, {
      wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
    });
    expect(screen.getAllByText('MyList')).toBeTruthy();
    expect(store.getActions()).toEqual([]);
  });
});

test result:

 PASS  examples/69013562/index.test.tsx (10.158 s)
  69013562
    ✓ should render AccessUnavailablePanel (28 ms)
    ✓ should render CircularLoader and dispatch get details action (3 ms)
    ✓ should render Redirect (2 ms)
    ✓ should render MyList (2 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 index.tsx |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        11.014 s

There is no single answer to the test strategy, adjust according to the specific situation.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.