9

I'm trying to write a test with jest and react testing library to see if the store updates a state and the new state is shown inside the component.

I have a component like:

import { getName } from 'src/store/actions/hello/getName';

const HelloComponent = () => {
    const dispatch = useDispatch();
    const { name, isLoading } = useSelector((state:RootState) => state.Hello)
    useEffect( () => {
       dispatch(getName());
    }, []);
    return (
        <div>
             { name &&
               Hello {name}
             }
        </div>
    )
}

There is the store which calls an API like:

const getName = () => (dispatch) => {
  const URL = getUrl()
  fetch(URL, {method: 'GET'})
    .then((response) => response.json())
    .then(({ data }) => dispatch({
      type: 'SAVE_NAME',
      payload: data.name
    })) # This action updates the name inside the redux state
};

I'm using mswjs to mock the API call and I'd like to test that after the component mount, the DOM shows 'Hello John'.

This is the test I've written, but it doesn't work:

it('shows the name', async () => {
   const {findByText} = renderWithStore(<HelloComponent />);
   expect(await findByText('Hello John')).toBeInTheDocument();
});

renderWithStore mocks the store.

import configureStore from 'redux-mock-store';
import { render as rtlRender } from '@testing-library/react'
import { initialState as Hello } from '../src/store/reducers/helloReducer';


const mockStore = configureStore()

const initialStateMock = {
   Hello
}
function render(
  ui
) {
  const store = mockStore(initialStateMock);
  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>
  }
  return rtlRender(ui, { wrapper: Wrapper })
}

It seems like it doesn't wait for the state to update.

any help is much appreciated

Thanks

disgra
  • 683
  • 3
  • 6
  • 18
  • what is your `renderWithStore()` function? can you show it? – Taghi Khavari Jan 13 '21 at 08:47
  • and you need to show how do you import the `getName()` function – Taghi Khavari Jan 13 '21 at 09:07
  • have you thought of testing store (actions) separately, and then component only with the case of calling `dispatch` with your action? (mocking both action function and `useDispatch` hook) – Emzaw Jan 13 '21 at 09:27
  • I'll try it if my solution is not feasible. But I would prefer to avoid mocking if possible, to make the test more "realistic". Here I'm mocking only the API call with mswjs. Moreover, I'd like to test what the user sees more than unit testing the action code. – disgra Jan 13 '21 at 09:50

1 Answers1

8

I think I have found the problem.

The redux-mock-store library doesn't allow to test the state change. The component inside is changing the "real" store state and not the mocked one but it is using the mocked store state when rendering which doesn't change.

In this test I don't need to pass an initial store different from the original and I can use the "real" store without mocking it:

 import {render} from '@testing-library/react'
 import store from 'path_to_the_app_store_obj';
 
 it('shows the name', async () => {
   const {findByText} = render(
        <Provider store={store}>
            <HelloComponent />
        </Provider>
    );
   expect(await findByText('Hello John')).toBeInTheDocument();
 });

Using the original store the test works.

In addition, sometimes you may want to wait for the store to change, in these cases, I found it useful to add:

 await act(() => sleep(500));

after fired the store action and before the "expect".

disgra
  • 683
  • 3
  • 6
  • 18