How React Testing Library can assert children props and Redux store updates?

Viewed 3901

I just started exploring a unit testing framework for my react application. I locked my choice at @testing-library/react instead of Enzyme as it is recommended by React and helps writing more robust tests. I'm also planning to use Jest for completeness.

But before I could proceed with React Testing Library, I'm stuck at few questions. Can someone please help?

For instance,

  1. There is an Editor component which updates Redux store based on user input.
  2. There is another component Preview which reads value directly from store.
  3. There is a third component Container which contains both of the above components passing some props.

Questions:

  1. How to test if user input in Editor component actually updates store?
  2. How to test if Preview component can render value reading from store?
  3. How to test if Container component passes props correctly to its children?


Container.jsx

import React from 'react';
import Editor from './Editor';
import Preview from './Preview';

const Container = ({editorType}) => {
  return (
    <div>
      <Editor type={editorType} />
      <Preview />
    </div>
  );
};
export default Container;

Editor.jsx

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {setName} from './actions';

const Editor = ({type}) => {
  const name = useSelector(state => state.name);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Type: {type}</h1>
      <input value={name} onChange={evt => dispatch(setName(evt.target.value))} />
    </div>
  );
};
export default Editor;

Preview.jsx

import React from 'react';
import { useSelector } from 'react-redux';

const Editor = ({type}) => {
  const name = useSelector(state => state.name);

  return (
    <div>
      <h1>Name: {name}</h1>
    </div>
  );
};
export default Preview;
1 Answers

Here is how I would do it :

How to test if user input in Editor component actually updates store?

Looking at your code, Editor does not update store, it does not call useDispatch. But assuming it does, using https://github.com/reduxjs/redux-mock-store I would setup a mocked store provider to the component in the test, then trigger user input and check the mocked store if the correct action have been dispatched, with the correct payload :

describe('<Editor />', () => {
  let store: MockStoreEnhanced;
  let rtl: RenderResult;

  beforeEach(() => {
    store = mockStore({ /* your store structure */ });

    rtl = render(
      <Provider store={store}>
        <Editor type={...} />
      </Provider>
    );
  });

  test('dispatch action correctly', () => {
    fireEvent.change(rtl.container.querySelector('input'), { target: {value: 'test' } });

    // check actions that were dispatched by change event
    // getActions return an array avec redux actions (type, payload)
    expect(store.getActions()).toEqual(...);
  }
}

Beside of this you should test your store reducer and action creators, to ensure that the action modifies the store correctly.

How to test if Preview component can render value reading from store?

Same principle, setup your test with a mocked store, init this store with relevant value, and just test that the component displays what's in the store :

describe('<Preview />', () => {
  let store: MockStoreEnhanced;
  let rtl: RenderResult;

   beforeEach(() => {
     store = mockStore({ name: 'This is name from store' });

     rtl = render(
       <Provider store={store}>
         <Preview type={...} />
       </Provider>
     );
   });

   test('displays store content correctly', () => {
     expect(rtl.getByText('This is name from store')).toBeTruthy();
   }
 }

How to test if Container component passes props correctly to its children?

Here you should test Container + Preview + Editor together, and find something visual to test (as a real user would perceive it), involving the props passed by Container to its children.

For example, if Preview displays props.type, which comes from Container editorType props :

describe('<Container />', () => {
  // mocked store may not be necessary here, depending on your use case
  let store: MockStoreEnhanced;
  let rtl: RenderResult;

   beforeEach(() => {
     store = mockStore({ name: 'This is name from store' });

     rtl = render(
       <Provider store={store}>
         <Container editorType={'toTest'} />
       </Provider>
     );
   });

   test('displays store content correctly', () => {
     // check that 'toTest' exists in DOM because it is rendered by Preview component, a child of Container
     expect(rtl.getByText('toTest')).toBeTruthy();
   }
 }

To sum it up, my approach would be :

  • Test actions and reducers alone (very simple to test, state machines, just plain Jest tests)
  • Test that components dispatch correct actions with correct payloads on correct user inputs
  • Mock store to setup relevant use cases (init store value) for components reading data from store

For the last 2 items I use https://github.com/reduxjs/redux-mock-store.

Related