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.
Related
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
I was testing useSelector using the following code below. Although when I looked at my coverage report I saw in red that it wasn't actually being tested... Is there a way to test those lines?
import React from 'react';
import { render } from '../../../test-utlities/test-utlities';
import IndividualInvestor from './IndividualInvestor';
import * as redux from 'react-redux';
describe('Individual Investor', () => {
const useSelectorMock = jest.spyOn(redux, 'useSelector');
const useDispatchMock = jest.spyOn(redux, 'useDispatch');
beforeEach(() => {
useSelectorMock.mockClear();
useDispatchMock.mockClear();
});
test('Makes sure it reaches questionaire', async () => {
useSelectorMock.mockReturnValue({
offering: {
payment_enabled: false,
_id: '61aa480e4e18c1bbfba8f83d',
},
payment_enabled: false,
_id: '61aa480e4e18c1bbfba8f83d',
});
const dummyDispatch = jest.fn();
useDispatchMock.mockReturnValue(dummyDispatch);
Yes, actually run the code :) By mocking useSelector, you've skipped running all of its actual logic that takes your selector function and runs it when needed.
We would strongly recommend against mocking useSelector and useDispatch. Instead, create an actual Redux store in the test, and wrap the component under test with a <Provider>.
You can see examples of how to do this in a reusable render() test util in our docs guide on testing:
https://redux.js.org/usage/writing-tests#components
In addition to what markerikson said i managed to actually test the code with this
wrapper for the component with the provider
// test-utils.js
import React from 'react';
import { render as rtlRender } from '#testing-library/react';
import { configureStore } from '#reduxjs/toolkit';
import { Provider } from 'react-redux';
// Import your own reducer
import rootReducer from '../redux/reducer/combineReducers';
import { BrowserRouter as Router } from 'react-router-dom';
function render(
ui,
{
preloadedState,
store = configureStore({ reducer: rootReducer, preloadedState }),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return (
<Provider store={store}>
<Router>{children}</Router>
</Provider>
);
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
// re-export everything
export * from '#testing-library/react';
// override render method
export { render };
Then i used that function render
const store = createStore(
rootReducer,
{
formBody: {
offering: {
data: {
payment_enabled: false,
_id: '61aa480e4e18c1bbfba8f83d',
},
},
},
},
applyMiddleware(thunk)
);
const { container } = render( <SomeComponent/>,
//Pass in store here!
{
store: store,
}
);
I got a custom hook which I want to test. It receives a redux store dispatch function and returns a function. In order to get the result I'm trying to do:
const { result } = renderHook(() => { useSaveAuthenticationDataToStorages(useDispatch())});
However, I get an error:
Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a
It happens because of the useDispatch and that there is no store connected. However, I don't have any component here to wrap with a provider.. I just need to test the hook which is simply saving data to a store.
How can I fix it?
The react hooks testing library docs go more into depth on this. However, what we essentially are missing is the provider which we can obtain by creating a wrapper. First we declare a component which will be our provider:
import { Provider } from 'react-redux'
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store={reduxStore}>{children}</Provider>
)
then in our test we call
test("...", () => {
const store = configureStore();
const wrapper = ({ children }) => (
<ReduxProvider reduxStore={store}>{children}</ReduxProvider>
);
const { result } = renderHook(() => {
useSaveAuthenticationDataToStorages(useDispatch());
}, { wrapper });
// ... Rest of the logic
});
This is probably a late answer but you can also use this in your test
jest.mock('react-redux', () => {
const ActualReactRedux = jest.requireActual('react-redux');
return {
...ActualReactRedux,
useSelector: jest.fn().mockImplementation(() => {
return mockState;
}),
};
});
This issues is related your test file. You have to declarer provider and store in your test file.
Update or replace your app.test.tsx by below code
NB: Don't forget to install redux-mock-store if you don't have already.
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
describe('With React Testing Library', () => {
const initialState = { output: 10 };
const mockStore = configureStore();
let store;
it('Shows "Hello world!"', () => {
store = mockStore(initialState);
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText('Hello World!')).not.toBeNull();
});
});
I got this solution after searching 1 hours.
Thanks a lot to OSTE
Original Solution: Github issues/8145 and solution link
With this solution if you get error like TypeError: window.matchMedia is not a function then solve by this way. add those line to your setupTests.ts file. Original solution link stackoverflow.com/a/64872224/5404861
global.matchMedia = global.matchMedia || function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};
I think you can create test-utils.[j|t]s(?x), or whatever you set the name of the file to, like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/test-utils.tsx
//root(or wherever your the file)/test-utils.tsx
import React from 'react';
import { render, RenderOptions } from '#testing-library/react';
import { Provider } from 'react-redux';
// Import your store
import { store } from '#/store';
const Wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
const customRender = (ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: Wrapper, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
Use it like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/pages/index.test.tsx
//__tests__/pages/index.test.tsx
import React from 'react';
import { render, screen } from '../test-utils';
import Home from '#/pages/index';
describe('Home Pages', () => {
test('Should be render', () => {
render(<Home />);
const getAText = screen.getByTestId('welcome');
expect(getAText).toBeInTheDocument();
});
});
Works for me.
screenshot work
BTW, if you place the test-utils.[j|t]s(?x) or whatever you set the name file place on the directory __test__, don't forget to ignore it on jest.config.js.
//jest.config.js
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/__tests__/test-utils.tsx'],
repo: https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart
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
I have this container
import { connect } from 'react-redux'
import { createType, getTypes } from '../modules/type'
import Type from '../components/Type'
const mapDispatchToProps = {
createType,
getTypes
}
const mapStateToProps = state => ({
types: state.type.types
})
export default connect(mapStateToProps, mapDispatchToProps)(Type)
and I would like to test it using enzyme. To do it, I'm using this test
import React from 'react'
import { Provider } from 'react-redux'
import { mount } from 'enzyme'
import TypeContainer from 'routes/Type/containers/TypeContainer'
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const mockStore = configureMockStore([ thunk ]);
const mockStoreInitialized = mockStore({
type: {
types: [
{id: 1, name: 'type 1'}
]
}
});
describe.only('(Route) Type', () => {
it('should get container', () => {
const wrapper = mount(
<Provider store={mockStoreInitialized}>
<TypeContainer />
</Provider>
)
expect(wrapper.find(TypeContainer).prop('types')).to.deep.equal([{id: 1, name: 'type 1'}])
})
})
The test is failing (at the assertion level) because wrapper.find(TypeContainer).props() is empty. I can not find find why.
The strange thing is that if I check the coverage report, my test passed into the mapStateToProps function.
Did I missed something ?
TypeContainer won't have a prop called types, it will pull types from the store and pass it to Type, which will have a prop called types. So it's not that mapStateToProps is not doing the right thing; it's just you're making assertions against the wrong object.