Testing components
When testing your components I strongly recommend the approach recommended by Kent C. Dodd's awesome Testing Javascript course, where you try to test the behaviour of your components using a natural DOM API, rather than reaching into the internals of your components.
He has published a very useful package by the name of @testing-library/react
which allows us to follow this paradigm whilst providing very useful mechanisms by which to interact with the DOM created by our React components.
The tests below shall be adopting this package and strategy.
Example
Imagine we were trying to test the following component.
function Counter() {
const count = useStoreState(state => state.count)
const increment = useStoreActions(actions => actions.increment)
return (
<div>
Count: <span data-testid="count">{count}</span>
<button type="button" onClick={increment}>
+
</button>
</div>
)
}
As you can see it is making use of our hooks to gain access to state and actions of our store.
We could adopt the following strategy to test it.
import { render } from '@testing-library/react'
import { createStore, StoreProvider } from 'easy-peasy'
import model from './model';
test('Counter', () => {
// arrange
const store = createStore(model)
const app = (
<StoreProvider store={store}>
<ComponentUnderTest />
</StoreProvider>
)
// act
const { getByTestId, getByText } = render(app)
// assert
expect(getByTestId('count').textContent).toEqual('0')
// act
fireEvent.click(getByText('+'))
// assert
expect(getByTestId('count').textContent).toEqual('1')
})
As you can see we create a store instance in the context of our test and wrap the component under test with the StoreProvider
. This allows our component to act against our store.
We then interact with our component using the DOM API exposed by the render.
This grants us great power in being able to test our components with a great degree of confidence that they will behave as expected.
Utilising initialState to predefine state
It is also to preload your store with some state by utilising the initialState
configuration property of the store. This may help you test specific conditions of your component.
test('Counter', () => {
// arrange
const store = createStore(model, { initialState: initialStateForTest })
// ...
})
Mocking calls to services
If your thunks make calls to external services we recommend encapsulating these services within a module and then exposing them to your store via the injection
configuration property of the store. Doing this will allow you to easily inject mock versions of your services when testing them.
test('saving a todo', () => {
// arrange
const mockTodoService = {
save: jest.fn()
};
const store = createStore(model, {
injections: {
todoService: mockTodoService
}
});
// ...
})