Redux toolkit - testing store.dispatch causes infinite loop - reactjs

I want to test async action changes store state correctly.
However, when I make configure store and use store.dispatch, it causes infinite loop and test ends with timeout error.
Here is my code.
import { configureStore } from "#reduxjs/toolkit";
import * as APIs from "../../common/APIs";
import { IRepository, repositoryFactory } from "../../common/Interfaces";
import reposReducer, { fetchRepositories, reposInitialState, toBeLoaded } from "./reposSlice";
import store from "../../app/store";
import authReducer from "../auth/authSlice";
jest.mock("../../common/APIs");
const mockedAPIs = APIs as jest.Mocked<typeof APIs>;
describe("reposSlice", () => {
const store = configureStore({
reducer: {
repos: reposReducer,
},
});
it("Should fetch repositories correctly", (done) => {
const repository : IRepository = repositoryFactory();
mockedAPIs.getRepositories.mockResolvedValue([repository]);
store.dispatch(fetchRepositories("")).then(() => {
expect(store.getState().repos.repoList.length).toBe(1);
});
});
});
I think my async action "fetchRepositories" act well.
Because when I write
console.log(current(state));
inside the builder.addCase, it print correct state.
Thanks.

Related

TypeError: Cannot read properties of undefined - React Redux Toolkit Testing

In my React project I'm using redux toolkit and all is working fine on the UI side, but I'm running into the following error now I've come to testing my code (Note - I have chosen RTL as my library of choice, and I know I should test as I write code but I'm here now anyway).
TypeError: Cannot read property 'isSearching' of undefined
16 | const postState = useSelector(selectInitialPostsState);
> 17 | const isSearching = postState.isSearching;
As can be seen above, I import my initialState and save it to postState in my component, and then access isSearching from there - which works absolutely fine in my code and my App. I have accessed my state properties this way throughout my code as it seemed easier than having to write an individual selector for each state property used.
In my test file, I have resorted to Redux's test writing docs and have tried two different ways of rendering my store via manipulating RTL's render() method. The first function written locally in my test file, which for the time being I have commented out, and the second is imported into my test file from test-utils. Both methods give me the same errors.
Here is my test file:
import React from 'react';
// import { Provider } from 'react-redux';
// import { render as rtlRender, screen, fireEvent, cleanup } from '#testing-library/react';
// import { store } from '../../app/store';
import { render, fireEvent, screen } from '../../../utilities/test-utils';
import Header from '../Header';
// const render = component => rtlRender(
// <Provider store={store}>
// {component}
// </Provider>
// );
describe('Header Component', () => {
// let component;
// beforeEach(() => {
// component = render(<Header />)
// });
it('renders without crashing', () => {
render(<Header />);
// expect(screen.getByText('Reddit')).toBeInTheDocument();
});
});
And here is my test-utils file:
import React from 'react';
import { render as rtlRender } from '#testing-library/react';
import { configureStore } from '#reduxjs/toolkit';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
// Import your own reducer
import sideNavReducer from '../src/features/sideNavSlice';
import postReducer from '../src/features/postSlice';
function render(
ui,
{
preloadedState,
store = configureStore({ reducer: { sideNav: sideNavReducer, posts: postReducer }, preloadedState }),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return (
<Provider store={store}>
<BrowserRouter>{children}</BrowserRouter>
</Provider>
)
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
//override render method
export { render }
I have been able to prevent this error by creating a selector that saves the property directly, and accessing it in my component as isSearching without having to use postState.isSearching, but then the next error comes up about the next property I have to do this with, and then the next and so on.
It seems that in my test file, postState is undefined, whereas in my console, it holds the initialState from my slice file. Can anyone please advise why this is? I don't see a reason why I would have to write numerous selectors that directly access properties, rather than accessing them through an initialState selector. I can't seem to grasp how it works in my functioning code but not in my test code.
For further context if required, here is the mentioned initialState and selector from my slice file:
const postSlice = createSlice({
name: 'posts',
initialState: {
allPosts: [],
popularPosts: [],
sportPosts: [],
newsPosts: [],
savedPosts: [],
hiddenPosts: [],
reportedPosts: [],
dataLoading: true,
allPostsShown: true,
ellipsisClicked: false,
reportModal: false,
modalClosed: true,
imgClicked: false,
searchedPostsFound: false,
searchedPosts: [],
isSearching: false,
searchText: "",
},
});
export const selectInitialPostsState = state => state.posts;
And here is how it is used in my component. I have only included the relevant code.
const SideNav = () => {
const postState = useSelector(selectInitialPostsState);
const isSearching = postState.isSearching;
useEffect(() => {
!isSearching && setSearchText("");
if(!searchText){
dispatch(userNoSearch());
dispatch(searchedPostsFound({ ids: undefined, text: searchText })); // reset searchedPosts state array
}
}, [dispatch, searchText, isSearching]);
useEffect(() => {
if(isSearching){
dispatch(searchedPostsFound());
}
}, [dispatch, isSearching, search]);
}
If you want to load the initialState of your component, you need to send it as a parameter to rtlRender with your component.
it('renders without crashing', () => {
rtlRender(<Header />, { preloadedState: mockStateHere });
// expect(screen.getByText('Reddit')).toBeInTheDocument();
});
your mockStateHere structure needs to resemble what your redux store's state looks like as best as possible, using entities and id's etc
If you look at how you are building the render function in this example, you'll see the param preloadedState being deconstructed along with store which has the default configuration in this example:
function render(
ui,
{
preloadedState,
store = configureStore({ reducer: { sideNav: sideNavReducer, posts: postReducer }, preloadedState }),
...renderOptions
} = {}
...
)
that's the same preloadedState value that you send in with your ui component, which is being loaded into your store with the configureStore method and that store value will be sent into the Provider

adding 'dispatch' to a redux action breaks action (w/out dispatch the action runs)

I am using redux with redux-thunk middleware. The function in question makes a GET request to an API and upon response (.then()) dispatches the res to my redux store via an action.
For some reason when I pass dispatch to the parent function the function never runs. When I remove dispatch the parent function does run...(???) I have multiple other components within the same app that follow this exact same pattern successfully. For some reason this particular component is behaving in this strange way although i've triple checked and the boilerplate is all the same.
Here is my store.jsx:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from '../reducers/root_reducer'
const configureStore = (preloadedState = {}) =>
createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, logger)
);
export default configureStore;
my actions my_team_actions.js:
import * as APIUtil from '../util/api/my_team_api_util';
export const RECEIVE_ORG_SURVEY = "RECEIVE_ORG_SURVEY"
export const receiveOrgSurvey = survey => ({
type: RECEIVE_ORG_SURVEY,
survey
});
export const getOrganizationSurvey = () => dispatch => {
debugger
APIUtil.getOrgSurvey()
.then((res) => {
debugger
dispatch(receiveOrgSurvey(res))
})
.catch(err => console.log(err))
}
my API call my_team_api_util.js:
import axios from 'axios';
export const getOrgSurvey = () => {
return axios.get(`/api/mongo/organizations/test`)
}
component container my_team_container.jsx:
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => getOrganizationSurvey(),
};
};
export default connect(mSTP, mDTP)(MyTeam);
component my_team.jsx:
import React from 'react';
class MyTeam extends React.Component {
constructor(props) {
super(props)
this.createTeam = this.createTeam.bind(this);
}
createTeam() {
this.props.getSurvey();
}
render() {
return (
<div className="my-team-frame frame">
<div className="my-team-container">
<div className="contact-data-container">
<div className="contact-data-header header">Contact a Data Scientist</div>
</div>
<div className="myteam" onClick={this.createTeam}>BUTTON</div>
</div>
</div>
)
}
}
export default MyTeam;
On the client side the my_team component renders fine and when I click the button which calls the function which will eventually dispatch my action it only seems to run when dispatch is NOT included in getOrganizationSurvey() in my_team_actions.js i.e. I hit both debuggers (and the second one with a correct res object). When dispatch is included (as shown in the snippet above) I don't hit either debuggers nor are any errors thrown.
I'm really scratching my head on this, any input is appreciated!
Thanks,
Ara
God I am a moron... XD
I said I triple checked... I should have checked 4 times! The issue was in my components container my_team_container.jsx I simply forgot to pass dispatch in the map dispatch to props object!
I fixed it by adding dispatch to the getSurvey callback...
my_team_container.jsx
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => dispatch(getOrganizationSurvey()),
};
};
export default connect(mSTP, mDTP)(MyTeam);
it's funny how you can spend 2 hours on a problem, think it's hopeless and then as soon as you ask for help take another look at it and the solution just stares right back at you 😂

How to access the Redux store outside of a component in React

I am beginning with Redux and I always used it in components with connect() and mapStateToProps(), but now I want to call my API with setInterval() every x time to check if the server has new data not stored in the Redux store, and substitute it.
My approach was to create a function that reads the store and update it like that:
import { store } from './dir/dir/store.js'
const refresher = async () => {
const state = store.getState();
// Call API, compare 'state.calendar' with calendar from server
// Call store.dispatch() if they are different, then update Redux store
}
export default refresher
My questions are:
Is this a good practise to use Redux?
Is there a better approach to this problem?
Thanks
It's perfectly fine to export the store and use within a vanilla js/ts file.
Example Redux Store
Make sure to export the store that you create
import { configureStore } from "#reduxjs/toolkit";
import { slice } from "../features/counterSlice";
export const store = configureStore({
reducer: {
counter: slice.reducer
}
});
Usage in Non-Component Code:
You can then import the created store in any other code
import { store } from "../App/store";
import { slice as counterSlice } from "../features/counterSlice";
export function getCount(): number {
const state = store.getState();
return state.counter.value;
}
export function incrementCount() {
store.dispatch(counterSlice.actions.increment());
}
Traditional Usage in Functional Component
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../App/store";
import { slice as counterSlice } from "../features/counterSlice";
export function Clicker() {
const dispatch = useDispatch();
const count = useSelector((state: RootState) => state.counter.value);
const dispatchIncrement = () => dispatch(counterSlice.actions.increment())
// ...
Example Slice
import { createSlice } from "#reduxjs/toolkit";
export const slice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
}
}
});
Demo in Codesandbox
Note: You cannot use this option with Server Side Rendering. If you need to support SSR, you can use middleware to listen to dispatched actions and handle elsewhere.
Further Reading
What is the best way to access redux store outside a react component? | Stack Overflow
Access the Redux Store Outside a React Component | Blog
How can I access the store in non react components? | Github Issues
Here you can access the store and action out side any component like index.js file in react-native.
import {updateLocationAlertItem} from './src/store/actions/locationAlertAction';
import {store} from './src/store/index';
store.subscribe(listener);
function listener() {
state = store.getState().locationAlertReducer;
}
store.dispatch(
updateLocationAlertItem({
index: index,
is_in_radius: true,
is_notification: true,
arrival_time: moment().format('DD/MM/YYYY hh:mm'),
exit_time: item.exit_time,
}),
);

How to mock store in redux toolkit

import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { render, screen, fireEvent } from '#testing-library/react';
import MyApp from './MyApp ';
const initialState = {};
const mockStore = configureStore(initialState);
describe('<MyApp />', () => {
it('click button and shows modal', () => {
render(
<Provider store={mockStore}>
<MyApp />
</Provider>
);
fireEvent.click(screen.getByText('ADD MIOU'));
expect(queryByText('Add MIOU Setting')).toBeInTheDocument();
});
});
I am using jest and redux toolkit with reactjs, and trying to mock a store to write a test.
But got the following error
TypeError: store.getState is not a function
Is there any way to fix this? Am I missing something?
I assume you are trying to test a connected component, and you expect (1) action creators and reducers to be run and (2) redux state to be updated as part of your test?
I have not used redux-mock-store, but I see the following note on their documentation, which leads me to believe this library may not work the way you expect:
Please note that this library is designed to test the action-related logic, not the reducer-related one. In other words, it does not update the Redux store.
I suggest you try this approach for testing connected components. I have used this approach to write tests that update redux state and render connected components.
First, you override the RTL render method:
// test-utils.js
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'
function render(
ui,
{
initialState,
store = createStore(reducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Then you reference that new render method instead of RTL directly. You can also provide initial state for your test.
import React from 'react'
// We're using our own custom render function and not RTL's render
// our custom utils also re-export everything from RTL
// so we can import fireEvent and screen here as well
import { render, fireEvent, screen } from '../../test-utils'
import App from '../../containers/App'
it('Renders the connected app with initialState', () => {
render(<App />, { initialState: { user: 'Redux User' } })
expect(screen.getByText(/redux user/i)).toBeInTheDocument()
})
(All code copied from redux.js.org.)
I was having the same problem as you but thanks to #srk for linking the Redux docs and, the React Testing Library docs, I found a pretty good solution that worked for me with TypeScript:
// store.ts - just for better understanding
export const store = configureStore({
reducer: { user: userReducer },
})
export type RootState = ReturnType<typeof store.getState>
// test-utils.ts
import React, { ReactElement } from 'react'
import { Provider } from 'react-redux'
import { render as rtlRender, RenderOptions } from '#testing-library/react'
import {
configureStore,
EmptyObject,
EnhancedStore,
PreloadedState,
} from '#reduxjs/toolkit'
// import your reducers
import userReducer from 'features/user/user.slice'
import type { RootState } from 'app/store'
// ReducerTypes is just a grouping of each slice type,
// in this example i'm just passing down a User Reducer/State.
// With this, you can define the type for your store.
// The type of a configureStore() is called EnhancedStore,
// which in turn receives the store state as a generic (the same from store.getState()).
type ReducerTypes = Pick<RootState, 'user'>
type TStore = EnhancedStore<ReducerTypes>
type CustomRenderOptions = {
preloadedState?: PreloadedState<ReducerTypes & EmptyObject>
store?: TStore
} & Omit<RenderOptions, 'wrapper'>
function render(ui: ReactElement, options?: CustomRenderOptions) {
const { preloadedState } = options || {}
const store =
options?.store ||
configureStore({
reducer: {
user: userReducer,
},
preloadedState,
})
function Wrapper({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...options })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Then you just have to pass down an object with the preloadedState property as the second parameter to your render; you can even define a new store inside the render if you want with the "store" property.
describe('[Component] Home', () => {
it('User not logged', () => {
const component = render(<Home />)
expect(component.getByText(/User is: undefined/i)).toBeInTheDocument()
})
it('User logged in', () => {
const component = render(<Home />, {
preloadedState: { user: { name: 'John' /* ...other user stuff */ } },
})
expect(component.getByText(/User is: John/i)).toBeInTheDocument()
})
})
I couldn't find anywhere else to paste my findings regarding redux toolkit and redux-mock-store.
In order to dispatch async thunks and test results you can specify the type of dispatch when creating the mock store.
import configureStore from 'redux-mock-store';
import { getDefaultMiddleware } from '#reduxjs/toolkit'
const middlewares = getDefaultMiddleware();
const mockStore = createMockStore<IRootState, AppDispatch>(middlewares);
describe('my thunk action', () => {
const store = mockStore();
test('calls my service', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(myService).toBeCalledWith({ id: 32 });
});
test('contains foo bar actions', async() => {
await store.dispatch(myThunk({ id: 32 }));
expect(store.getActions()).toEqual(....);
});
});
As of January 2023 it is no longer recommended to mock the store in redux, see the docs here and this answer for more information.

is there another way to mock component's mapDispatchToProps when using Jest

I currently have a component like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getDataAction } from ' './my-component';
export class MyComponent extends { Component } {
componentWillMount() {
this.props.getData();
}
render(){
<div>
this.props.title
</div>
}
}
const mapStateToProps = (state) => ({
title: state.title
});
const mapDispatchToProps = (dispatch) ({
getData() {
dispatch(getDataAction());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
and I am trying to shallow render test it using jest and enzyme.
test:
import React from 'react';
import { shallow } from 'enzyme';
import { MyComponent } from './index';
it('renders without crashing', () => {
shallow(<MyComponent getData={jest.fn()} />);
});
My question is, is this the conventional way to mock? Jest official docs don't mention specifically about mocking props and this post Using Jest to mock a React component with props is about testing with full mounting instead.
Is there another way to mock dispatchToProps? In this example there is only one, but what if I have a lot of functions in dispatchToProps?
Side Question: in my real file, I have a reference to a value like this.props.information.value which I expect to throw an error like cannot get value of undefined since information is not mocked/defined, but it doesn't. It's only when functions are not present that an error is thrown.
You can export mapDispatchToProps and write tests for it by importing it in your tests.
Add export { mapDispatchToProps }; at the end of your MyComponent.js
Create MyComponent.tests.js file beside MyComponent.js
import configureMockStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
import { mapDispatchToProps } from './MyComponent';
const configMockStore = configureMockStore([thunkMiddleware]);
const storeMockData = {};
const mockStore = configMockStore(storeMockData);
describe('mapDispatchToProps', () => {
it('should map getDataAction action to getData prop', () => {
// arrange
const expectedActions = [getDataAction.type];
const dispatchMappedProps = mapDispatchToProps(mockStore.dispatch);
// act
dispatchMappedProps.getData();
// assert
expect(mockStore.getActions().map(action => action.type)).toEqual(expectedActions);
}
});
Here I have used thunk, just to let you know that how to do it if there are middlewares configured in your store setup.
Here getDataAction can also be a function instead of a simple action like { type: 'FETCH_DATA' } if you are using middlewares like thunks. However, the approach to test is same except that you will create expectedActions with explicit action types like const expectedActions = ['FETCH_CONTACTS']
Here FETCH_CONTACT is another action dispatched in your thunk i.e getDataAction

Resources