Reputation: 301
I have a function that calls useSelector multiple times upon rendering. In mocking the selector I used:
jest.spyOn(Redux, 'useSelector').mockReturnValueOnce(data).mockReturnValueOnce(moreData);
This changes the order of how hooks are called in my component. I have also tried creating a mock store and sending that into rendered component in the test as such:
const state = { userGuid: 'testGuid', user };
const store = mockStore(state);
jest.spyOn(Redux, 'useSelector').mockImplementation(() => store.getState());
const { getByTestId } = wrappedRender(ProfileScreen, mockProps, { store });
But this wraps the data in an extra object which my component cannot de-structure.
As of now I cannot find any other way to mock the return values of multiple useSelector calls without changing the order of hooks called. Any help is appreciated.
Upvotes: 5
Views: 7383
Reputation: 423
you shouldn't mock useSelector and useDispatch as eplained in the redux docs here: https://redux.js.org/usage/writing-tests… but you could control the intialstate pased in to store in your test or dispatch action in your test which should be more desirable… and this is how i implemented it following the docs.
in store/store.js i created a function that returns a configure store function so i can pass in an initial state for the store
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { reducer } from "./yourSlice";
import { reducerTwo } from "./anotherSlice";
const rootReducer = combineReducers({
authState: reducer,
mode: reducerTwo
});
export default function setUpStore(preloadedState) {
return configureStore({ reducer: rootReducer, preloadedState });
}
in utils/RTKOveride.js
import { render } from "@testing-library/react";
import { Provider } from "react-redux";
import setUpStore from "store/store";
function renderWithWrapers(ui,
{
preloadedState = {},
store = setUpStore(preloadedState),
...options
} = {},
) {
function Wrapper({ children }) {
return (
<Provider store={store}>
{children}
</Provider>
);
}
return { store, ...render(ui, { wrapper: Wrapper, ...options }) };
}
export * from "@testing-library/react";
export { renderWithWrapers as render };
i overide the render function from RTK, wrapped my components that will be passed via the props.children in the provider component so components using useSelector and useDispatch still function properly. even if you do not pass in a store or preloadedState(initialState) the code wont break as a store is created everytime the renderedWithWrapper is called
NOTE: the official react testing tookit explains how you could overide the render method and pass in additional options
then in your test/Component.test.js
import { render, screen } from "utils/RtkOveride";
import SomeComponent from "../Component";
test("some foo test", () => {
render(
<SomeComponent />
, { preloadedState: { authState: { state: true, refresh: "", access: "" } } }
);
const inputBar = screen.queryByText("foo", { exact: false });
expect(inputBar).toBeInTheDocument();
});
So you could pass in an initialState to change the default Slice InitialState or follow the other way the redux docs detailed... Hopes this helps.
Upvotes: 1
Reputation: 9624
Relying on mockReturnValueOnce
calls multiple times is a no-go as the order might change. The code below will just return the stubbed state no matter how many times useSelector
is being called.
import * as redux from 'react-redux'
const user = {
id: 1,
name: 'User',
}
const state = { user }
jest
.spyOn(redux, 'useSelector')
.mockImplementation((callback) => callback(state))
Upvotes: 6