Reputation: 12424
I want to test a custom hook which was implemented as an helping function for code reuse with other hooks. It's calling useDispatch
and useSelector
in its implementation, along with saving data in the session storage:
export function useCustomHook(key, obj)
{
let myObject = {
somefield: obj.someField
};
sessionStorage.setItem(key, JSON.stringify(myObject));
const dispatch = useDispatch();
dispatch(actionCreator.addAction(key, myObject));
}
And the test:
it('should have data in redux and session storage', () =>
{
const obj = {
somefield: 'my val',
};
renderHook(() => useCustomHook('some key', obj));
let savedObj= JSON.parse(sessionStorage.getItem('some key'));
expect(savedObj.someField).toBe('my val');
renderHook(() =>
{
console.log("INSIDE");
let reduxObj = useSelector(state => state.vals);
console.log("THE OBJECT: " );
console.log(reduxObj);
expect(reduxObj).toBe(2); //just to see if it fails the test - it's not
});
})
No matter what I try, the test only arrives to the "INSIDE" console log and does not print the "THE OBJECT: " console log. The test itself still passes so it's like the useSelector
somehow stops the rest of the renderHook execution.
I'm guessing it's related to the fact that the test doesn't have a store connected... What can be done to test redux in this case?
Upvotes: 5
Views: 9647
Reputation: 29654
You can write a simple extension of the documented redux renderWithProviders
function. This looks like:
import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import type { RenderOptions } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import type { PreloadedState } from '@reduxjs/toolkit'
import { Provider } from 'react-redux';
import { renderHook } from '@testing-library/react';
export function renderHookWithProviders<
Result,
Props>(
render: (initialProps: Props) => Result,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = configureStore({
reducer: {
....
},
preloadedState
}),
...renderOptions
}: ExtendedRenderOptions = {}
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>
}
return { store, ...renderHook(render, { wrapper: Wrapper, ...renderOptions }) }
}
Notice the render
method is replaced with renderHook
from @testing-library/react
. This can then be used simply in your tests:
describe('useGetRelated', () => {
test('Return related', () => {
const { result } = renderHookWithProviders(() => useGetRelated(), {
preloadedState: {
//initial state of redux
}
});
});
});
The hooks testing library talk about this briefly as well. Though the solution there requires more boiler plate in each test, the redux advocated solution seems better IMO.
Upvotes: 6
Reputation: 6944
You'd need to provide a wrapper
component that add the redux Provider
with a store
to connect to:
it('should have data in redux and session storage', () =>
{
const obj = {
somefield: 'my val',
};
const store = {} // create/mock a store
const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>
renderHook(() => useCustomHook('some key', obj), { wrapper });
let savedObj= JSON.parse(sessionStorage.getItem('some key'));
expect(savedObj.someField).toBe('my val');
renderHook(() => {
console.log("INSIDE");
let reduxObj = useSelector(state => state.vals);
console.log("THE OBJECT: " );
console.log(reduxObj);
expect(reduxObj).toBe(2); //just to see if it fails the test - it's not
}, { wrapper });
})
Just as a side note, the renderHook
is catching the errors, which is why you aren't seeing them in your test, if you had tried to access result.current
it would have thrown, and you could have seen it represented in result.error
, but the usage here of not returning a value from a custom hook to be asserted against is quite unusual.
This will likely also cause you issues by having the assertion inside second renderHook
call. You'll probably want to either return the value from the hook and assert outside, or assert the updated value in the redux store instead.
Upvotes: 1