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

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

Related

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.

Testing custom hook: Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

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

Jest / React / Redux - MapDispatchToProps Undefined

I am trying to learn React w/ Jest / Enzyme.
I have a component that receives 2 props -
loadTenantListAction,
filterTenantListAction,
These props are passed in via mapDispatchToProps -
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import {
loadTenantListAction,
filterTenantListAction,
} from '../../store/actions';
import TenantList from './TenantList';
const mapStateToProps = tenants => ({ tenants });
const mapDispatchToProps = {
loadTenantListAction,
filterTenantListAction,
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(TenantList)
);
I have declared propTypes in my component as such -
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class TenantList extends Component {
static propTypes = {
loadTenantListAction: PropTypes.func.isRequired,
filterTenantListAction: PropTypes.func.isRequired,
};
render() {
return <p>Foo</p>;
}
}
My unit test is failing now showing that these props are marked as required, but are undefined. I expect this, as I am not passing them into my test -
import React from 'react';
import { shallow } from 'enzyme';
import TenantListContainer from '../../../src/containers/TenantList';
import TenantList from '../../../src/containers/TenantList/TenantList';
describe('<TenantList />', () => {
it('should render the TenantList Component', () => {
const wrapper = shallow(<TenantListContainer />);
expect(wrapper.find(<TenantList />)).toBeTruthy();
});
});
I can pass the test doing something like
expect(
wrapper.find(
<TenantList
loadTenantListAction={() => {}}
filterTenantListAction={() => {}}
/>
)
).toBeTruthy();
But that does not seem right at all, nor do I expect to be able to write useful tests by carrying on like that.
How should I be handling props passed in via mapDispatchToProps?
You can pass props directly to your component in shallow method.
describe('<TenantList />', () => {
const props = {
loadTenantListAction: () => {}, // or you can use any spy if you want to check if it's called or not
filterTenantListAction () => {},
}
it('should render the TenantList Component', () => {
const wrapper = shallow(<TenantListContainer {...props} />);
expect(wrapper.find(<TenantList />)).toBeTruthy();
});
});

Why mapStateToProps do not map to the right prop?

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.

"This method is only meant to be run on single node. 0 found instead" Enzyme error

I have a simple component that toggles sorting. When a link is clicked, it fires a function. When I run shallow() on it, I'm getting an error like
Error: This method is only meant to be run on single node. 0 found instead
My component is:
import React, { Component, PropTypes } from 'react';
import { sortGames } from '../actions';
import { connect } from 'react-redux';
class SortList extends Component {
constructor(props) {
super(props);
this.onSortGames = this.props.onSortGames.bind(this);
}
componentWillMount(){
this.setState({
sortByIncrease: false
})
}
render() {
return (
<div className="sort">
<span>Sort by:
<a href="#" onClick={e => {
e.preventDefault();
this.props.onSortGames(this.props.filter, this.state.sortByIncrease);
this.setState({
sortByIncrease: !this.state.sortByIncrease
});
}}>
{ (this.state.sortByIncrease) ? "Decrease" : "Increase" }
</a>
</span>
</div>
)
}
}
SortList.propTypes = {
onSortGames: PropTypes.func.isRequired
};
const mapStateToProps = (state) => ({
games: state.games
});
const mapDispatchToProps = (dispatch) => ({
onSortGames(filter, asc) {
dispatch(sortGames(filter, asc));
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
and here is my test script:
import React from 'react';
import {expect} from 'chai';
import { shallow, mount } from 'enzyme';
import sinon from 'sinon';
import SortList from '../components/SortList';
import configureStore from '../configureStore';
describe('SortList', () => {
const store = configureStore();
const props = {
filter: "all",
sortByIncrease: false,
onSortGames : (a,b) => {}
};
it('should render sort list component', () => {
const wrapper = shallow(<SortList {...props} store={store}></SortList>);
expect(wrapper.length).to.equal(1);
});
it('should call sorting function when clicked', () => {
const onSortGames = sinon.spy();
const wrapper = shallow(<SortList {...props} store={store}></SortList>);
console.log(wrapper.debug());
wrapper.find('a').simulate('click');
expect(onSortGames.calledOnce).to.equal(true);
});
});
The console.log(wrapper.debug()); statement prints
<SortList filter="all" sortByIncrease={false} onSortGames={[Function]} store={{...}} games={{...}} />
What am I doing wrong? It has to be reaching the a tag, I believe, but still...
I find out shallow can't find child elements on connected components, so they export component and connected component separately.
Here's how they do on redux examples treeview example node component
so i changed my component like
class SortList extends Component {
to
export class SortList extends Component {
and
export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
to
const SortListConnected = export default connect(
mapStateToProps,
mapDispatchToProps
)(SortList);
export default SortListConnected;
changed my test scripts import to
import {SortList} from '../components/SortList';
after that find() worked well.
You are creating a sinon spy but not introducing it into your shallow function. You need to pass onSortGames spy into props before you pass it to the component. As of right now when you click it is simply calling the function you listed in the properties in your describe clause onSortGames : (a,b) => {}.
Try directly applying the spy to the JSX, something like:
it('should call sorting function when clicked', () => {
const onSortGames = sinon.spy();
const wrapper = shallow(<SortList onSortGames={onSortGames} {...props} store={store}></SortList>);
console.log(wrapper.debug());
wrapper.find('a').simulate('click');
expect(onSortGames.calledOnce).to.equal(true);
});

Resources