Snapshot Testing With Jest, React and Redux - reactjs

I'm trying to create a snapshot of my component which is using some custom useSelector and useDispatch hooks my boss created.
import { createDispatchHook, createSelectorHook } from "react-redux";
const Context = React.createContext(null);
export default Context;
export const useDispatch = createDispatchHook(Context);
export const useSelector = createSelectorHook(Context);
In my component the useSelector & useDispatch hooks are being called so I used jest.mock() on the hooks but then I get thrown an error saying TypeError: (0 , _reactRedux.createDispatchHook) is not a function. I can't find any documentation on how to mock a custom hook or how to even fix this issue.
import React, { createContext } from 'react';
import renderer from 'react-test-renderer';
import DecisionSidebar from './DecisionSidebar';
import { cleanup } from '#testing-library/react';
jest.mock('react-redux', () => ({
useDispatch: () => { },
useSelector: () => ({
project: {
myId: 0,
isProjectAdmin: true,
}
}),
}));
afterEach(cleanup);
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer.create(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
I have also tried this in my jest file which produces a different error (which I have tried to fix since this one is easier and there are a lot of fixes, but still no luck)
const mockContext = React.createContext(null);
const mockCreateDispatchHook = () => new createDispatchHook()
const mockCreateSelectorHook = () => new createSelectorHook();
jest.mock('react-redux', () => ({
...jest.requireActual("react-redux"),
useSelector: () => mockCreateSelectorHook(mockContext),
useDispatch: () => mockCreateDispatchHook(mockContext),
}));
Using the the way from the redux website as suggested
import React from "react";
import { render } from "#testing-library/react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from 'reducers';
const Context = React.createContext(null);
const renderer = (
ui,
{
initialState,
store = createStore(rootReducer, initialState, applyMiddleware(thunk)),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider
store={store}
context={Context}
>
{children}
</Provider>
);
}
return render(ui, { wrapper: Wrapper, ...renderOptions });
};
export * from "#testing-library/react";
export { renderer }
describe('DecisionSidebar Snapshot Test', () => {
it('renders correctly with data', () => {
const component = renderer(<DecisionSidebar />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Still produces the same error

Related

React Unit test using jest failed

I am doing Unit tests with jest and enzyme. I have following connected component with hooks.
I called redux actions to load data.
import React, {useEffect, useState, useCallBack} from "react";
import {connect} from "react-redux";
import CustomComponent from "../Folder";
import { loadData, createData, updateData } from "../../redux/actions";
const AccountComponent = (props) => {
const total = 50;
const [aIndex, setAIndex] = useState(1);
const [arr, setArr] = useState(['ds,dsf']);
//... some state variables here
const getData = () => {
props.loadData(aIndex, total, arr);
}
useEffect(() => {
getData();
},[aIndex, total])
//some other useEffect and useCallback
return(
<React.Fragment>
<CustomComponent {...someParam}/>
<div>
...
</div>
</React.Fragment>
)
}
const mapStateToProps = (state) => {
const { param1, param2, parma3 } = state.AccountData;
return {
param1,
param2,
parma3
}
}
export default connect(mapStateToProps, { loadData, createData, updateData })(AccountComponent)
Here, like following I created some test case for above component.
import AccountComponent from "../";
import React from "react";
import renderer from "react-test-renderer"
describe("AccountComponent component", () => {
const loadData = jest.fn();
let wrapper;
it("snapshot testing", () => {
const tree = renderer.create(<AccountComponent loadData={loadData} />).toJSON();
expect(tree).toMatchSnapshot();
})
beforeEach(() => {
wrapper = shallow(<AccountComponent loadData={loadData} />).instance();
});
it('should call loadData', () => {
expect(wrapper.loadData).toHaveBeenCalled();
});
})
But, It doesn't pass and shows error.
Error for snapshot testing:
invariant violation element type is invalid: expected string or a class/function
Error for method call testing:
Cannot read property 'loadData' of undefined.
Enzyme Internal error: Enzyme expects and adapter to be configured, but found none. ...
Not sure what the issue as I am not good in unit testing.
I am using react-redux 7.
Any help would be greatly appreciated.
Edit:
I also tried with provider like following. But, didn't help.
import { Provider } from "react-redux";
import {createStore} from "redux";
import reducer from "../../reducers";
const store = createStore(reducer);
it("snapshot testing", () => {
const tree = renderer.create(<Provider store={store}><AccountComponent loadData={loadData} /></Provider>).toJSON();
expect(tree).toMatchSnapshot();
})
beforeEach(() => {
wrapper = shallow(<Provider store={store}><AccountComponent loadData={loadData} /></Provider>).instance();
});
In your case when you are using connected components in the same file you need to pass the state through Provider. Also, you need to configure your enzyme. And finally, when you are using react hooks, you will need to do asynchronous unit tests, because effects are async. When you are trying to check if any function has been called you need to "spy" on it.
import configureStore from 'redux-mock-store';
import React from 'react';
import renderer from 'react-test-renderer';
import Enzyme, { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import Adapter from 'enzyme-adapter-react-16';
import { act } from 'react-dom/test-utils';
import createSagaMiddleware from 'redux-saga';
import AccountComponent from '../AccountComponent';
import * as actions from '../../../redux/actions';
jest.mock('../../../redux/actions', () => ({
loadData: jest.fn(),
createData: jest.fn(),
updateData: jest.fn(),
}));
const loadData = jest.spyOn(actions, 'loadData');
// configure Enzyme
Enzyme.configure({ adapter: new Adapter() });
const configureMockStore = configureStore([createSagaMiddleware]);
const initialState = {
AccountData: {
param1: 'param1',
param2: 'param2',
parma3: 'parma3 ',
},
};
const store = configureMockStore(initialState);
describe('AccountComponent component', () => {
let wrapper;
it('snapshot testing', () => {
const tree = renderer
.create(
<Provider store={store}>
<AccountComponent />
</Provider>,
)
.toJSON();
expect(tree).toMatchSnapshot();
});
beforeEach(async () => {
await act(async () => {
wrapper = shallow(
<Provider store={store}>
<AccountComponent />
</Provider>,
);
});
await act(async () => {
wrapper.update();
});
});
it('should call loadData', () => {
expect(loadData).toHaveBeenCalled();
});
});
Please mock your AccountData state with properties which will be used in that component. Also, I am not sure where is your test file is located, so you might need to change import path from '../../../redux/actions' to you actions file path. Finally, I am not sure what middleware you are using, so fill free to replace import createSagaMiddleware from 'redux-saga'; with your middleware for redux.
If you are using react it already comes with #testing-library and you don't need enzyme to do snapshot testing. This is how I do my snapshot testing.
import React, { Suspense } from "react";
import { screen } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import AccountComponent from "../";
import store from "./store";// you can mock the store if your prefer too
describe("<AccountComponent />", () => {
test("it should match snapshot", async () => {
expect.assertions(1);
const { asFragment } = await render(
<Suspense fallback="Test Loading ...">
<Provider store={store}>
<AccountComponent />
</Provider>
</Suspense>
);
expect(asFragment()).toMatchSnapshot();
});
});
When it is a functional component and you are using hooks, unit tests may not work with shallow rendering. You have to use 'renderHooks' instead to create a wrapper. Please refer https://react-hooks-testing-library.com/ for more details.

Mocking Auth0Client in #auth0 in jest

I'm trying to mock out the import { Auth0Provider } from "#auth0/auth0-react"; in a react app and keep running into issues with this error [Error: For security reasons, window.crypto is required to run auth0-spa-js.] From what I can tell this error is coming from the Auth0Client which gets imported from import { Auth0Client } from '#auth0/auth0-spa-js'; My thought is to mock out the scoped module #auth0/auth0-spa-js and do something like this
const handleRedirectCallback = jest.fn(() => ({ appState: {} }));
const buildLogoutUrl = jest.fn();
const buildAuthorizeUrl = jest.fn();
const checkSession = jest.fn();
const getTokenSilently = jest.fn();
const getTokenWithPopup = jest.fn();
const getUser = jest.fn();
const getIdTokenClaims = jest.fn();
const isAuthenticated = jest.fn(() => false);
const loginWithPopup = jest.fn();
const loginWithRedirect = jest.fn();
const logout = jest.fn();
export const Auth0Client = jest.fn(() => {
return {
buildAuthorizeUrl,
buildLogoutUrl,
checkSession,
handleRedirectCallback,
getTokenSilently,
getTokenWithPopup,
getUser,
getIdTokenClaims,
isAuthenticated,
loginWithPopup,
loginWithRedirect,
logout,
};
});
This seems to be working if I import the Auth0Client into any of my tests, but the problem is that the Auth0Provider is still importing the non mocked out client. Is there anyway to get the Auth0Provider to import the mocked out Auth0Client instead of the actual implementation? The file that uses the Auth0Provider looks like this
// 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 { MockedProvider } from '#apollo/client/testing';
import { Auth0Provider } from "#auth0/auth0-react";
import { Auth0Client } from '#auth0/auth0-spa-js';
// Import your own reducer
import applicationsReducer from "../app/slices/applications.slice";
import { UserProvider } from "../user-context";
import {getUserMock} from "../__tests__/apollo_mocks/get_user.mock"
// const MockedAuth0Provider = jest.requireActual("#auth0/auth0-react").Auth0Provider
function render(
ui,
{
initialState,
mocks,
store = createStore(applicationsReducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Auth0Provider clientId="__test_client_id__" domain="__test_domain__">
<Provider store={store}>
<MockedProvider mocks={[getUserMock, ...mocks]} addTypename={false}>
<UserProvider>{children}</UserProvider>
</MockedProvider>
</Provider>
</Auth0Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Hopefully this helps someone else but I ended up mocking the provider as well as some of the other auth0 modules that I was using this way
jest.mock('#auth0/auth0-react', () => ({
Auth0Provider: ({ children }) => children,
withAuthenticationRequired: ((component, _) => component),
useAuth0: () => {
return {
isLoading: false,
user: { sub: "foobar" },
isAuthenticated: true,
loginWithRedirect: jest.fn()
}
}
}));
This is all in my setupTests.js file

React component not setting state after mocking redux

Here is my test
const initialRootState = {
accounts: [mockAccounts],
isLoading: false
}
describe('Account Dashboard', () => {
let rootState = {
...initialRootState
}
const mockStore = configureStore()
const store = mockStore({ ...rootState })
const mockFunction = jest.fn()
jest.spyOn(Redux, 'useDispatch').mockImplementation(() => mockFunction)
jest
.spyOn(Redux, 'useSelector')
.mockImplementation((state) => state(store.getState()))
afterEach(() => {
mockFunction.mockClear()
// Reseting state
rootState = {
...initialRootState
}
})
it('renders correctly', () => {
const wrapper = mount(
<TestWrapper>
<AccountDashboard />
</TestWrapper>
)
console.log(wrapper)
})
})
In my component I am mapping accounts from the state. In my test I am getting the following error TypeError: Cannot read property 'map' of undefined
I would like to test an if statement I am using in my component to ensure it's returning the proper view based on the number of accounts I am receiving.
However, when I console.log(store.getState()) it is printing correctly. What am I doing wrong?
If you're going to test a Redux connected component, I'd recommend steering away from mocking its internals and instead to test it as if it were a React component connected to a real Redux store.
For example, here's a factory function for mounting connected components with enzyme:
utils/withRedux.jsx
import * as React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import rootReducer from "../path/to/reducers";
/*
You can skip recreating this "store" by importing/exporting
the real "store" from wherever you defined it in your app
*/
export const store = createStore(rootReducer);
/**
* Factory function to create a mounted Redux connected wrapper for a React component
*
* #param {ReactNode} Component - the Component to be mounted via Enzyme
* #function createElement - Creates a wrapper around the passed in component with incoming props so that we can still use "wrapper.setProps" on the root
* #returns {ReactWrapper} - a mounted React component with a Redux store.
*/
export const withRedux = Component =>
mount(
React.createElement(props => (
<Provider store={store}>
{React.cloneElement(Component, props)}
</Provider>
)),
options
);
export default withRedux;
Now, using the above factory function, we can test the connected component by simply using store.dispatch:
tests/ConnectedComponent.jsx
import * as React from "react";
import withRedux, { store } from "../path/to/utils/withRedux";
import ConnectedComponent from "../index";
const fakeAccountData = [{...}, {...}, {...}];
describe("Connected Component", () => {
let wrapper;
beforeEach(() => {
wrapper = withRedux(<ConnectedComponent />);
});
it("initially shows a loading indicator", () => {
expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
});
it("displays the accounts when data is present", () => {
/*
Ideally, you'll be dispatching an action function for simplicity
For example: store.dispatch(setAccounts(fakeAccountData));
But for ease of readability, I've written it out below.
*/
store.dispatch({ type: "ACCOUNTS/LOADED", accounts: fakeAccountData }));
// update the component to reflect the prop changes
wrapper.update();
expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
expect(wrapper.find(".accounts").exists()).toBeTruthy();
});
});
This vastly simplifies not having to mock the store/useSelector/useDispatch over and over when you start to test other Redux connected components.
On a side note, you can skip this entirely if you use react-redux's connect function while exporting the unconnected component. Instead of importing the default export, you can import the unconnected component within your test...
Example component:
import * as React from "react";
import { connect } from "react-redux";
export const Example = ({ accounts, isLoading }) => { ... };
const mapStateToProps = state => ({ ... });
const mapDispatchToProps = { ... };
export default connect(mapStateToProps, mapDispatchToProps)(Example);
Example test:
import * as React from "react";
import { mount } from "enzyme";
import { Example } from "../index";
const initProps = {
accounts: [],
isLoading: true
};
const fakeAccountData = [{...}, {...}, {...}];
describe("Unconnected Example Component", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<Example {...initProps } />);
});
it("initially shows a loading indicator", () => {
expect(wrapper.find(".loading-indicator")).exists().toBeTruthy();
});
it("displays the accounts when data is present", () => {
wrapper.setProps({ accounts: fakeAccountData, isLoading: false });
wrapper.update();
expect(wrapper.find(".loading-indicator")).exists().toBeFalsy();
expect(wrapper.find(".accounts").exists()).toBeTruthy();
});
});
I figured out that my test was working incorrectly due to my selector function in my component being implement incorrectly. So the test was actually working properly!
Note: My team is currently using mocked data(waiting for the API team to finish up endpoints).
Originally the useSelector function in my component(that I was testing) looked like:
const { accounts, isLoading } = useSelector(
(state: RootState) => state.accounts,
shallowEqual
)
When I updated this to:
const { accounts, isAccountsLoading } = useSelector(
(state: RootState) => ({
accounts: state.accounts.accounts,
isAccountsLoading: state.accounts.isLoading
}),
shallowEqual
)
My tests worked - here are my final tests:
describe('App', () => {
let rootState = {
...initialState
}
const mockStore = configureStore()
const store = mockStore({ ...rootState })
jest.spyOn(Redux, 'useDispatch').mockImplementation(() => jest.fn())
jest
.spyOn(Redux, 'useSelector')
.mockImplementation((state) => state(store.getState()))
afterEach(() => {
jest.clearAllMocks()
// Resetting State
rootState = {
...initialState
}
})
it('renders correctly', () => {
const wrapper = mount(
<TestWrapper>
<Dashboard />
</TestWrapper>
)
expect(wrapper.find('[data-test="app"]').exists()).toBe(true)
expect(wrapper.find(verticalCard).exists()).toBe(false)
expect(wrapper.find(horizontalCard).exists()).toBe(true)
})
it('renders multiple properly', () => {
rootState.info = mockData.info
const wrapper = mount(
<TestWrapper>
<Dashboard />
</TestWrapper>
)
expect(wrapper.find(verticalCard).exists()).toBe(true)
expect(wrapper.find(horizontalCard).exists()).toBe(false)
})
})

Testing a React with React-Router v.5, useHistory, useSelector and useEffect

I struggle with writing a proper test for a component that protects some routes and programatically redirects unauthorized users. The component looks like this:
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { isAuthed } from 'redux/selectors/auth';
const mapState = state => ({
isAuthenticated: isAuthed(state),
});
const LoginShield = ({ children }) => {
const { isAuthenticated } = useSelector(mapState);
const history = useHistory();
useEffect(() => {
if (!isAuthenticated) {
history.push('/login');
}
}, [isAuthenticated]);
return children;
};
export default LoginShield;
I basically would like to check that the component redirects unauthenticated user and doesn't redirect an authenticated user (two basic test cases). I tried several approaches using Jest/Enzyme or Jest/ReactTestingLibrary and cannot find a good solution.
For now my test is a mess but I will share it so that someone can show me where the problem lays:
import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import rootReducer from 'redux/reducers';
import LoginShield from 'components/LoginShield/LoginShield';
describe('LoginShield component', () => {
let wrapper;
let historyMock;
beforeEach(() => {
const initialState = { auth: { loginId: 'Foo' } };
const store = createStore(rootReducer, initialState);
historyMock = {
push: jest.fn(),
location: {},
listen: jest.fn(),
};
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
wrapper = mount(
<Provider store={store}>
<Router history={historyMock}>
<LoginShield>
<h5>Hello Component</h5>
</LoginShield>
</Router>
</Provider>,
);
});
it('renders its children', () => {
expect(wrapper.find('h5').text()).toEqual('Hello Component');
});
it('redirects to the login page if user is not authenticated', async () => {
await act(async () => {
await Promise.resolve(wrapper);
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
});
// is the above necessary?
console.log(historyMock.push.mock.calls);
// returns empty array
// ... ?
});
it('doesn`t redirect authenticated users', () => {
// .... ?
});
});
Any tips are more than welcome! Thank you in advance. :)

How to test a component using react-redux hooks with Enzyme?

I have a simple Todo component that utilizes react-redux hooks that I'm testing using enzyme but I'm getting either an error or an empty object with a shallow render as noted below.
What is the correct way to test components using hooks from react-redux?
Todos.js
const Todos = () => {
const { todos } = useSelector(state => state);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
Todos.test.js v1
...
it('renders without crashing', () => {
const wrapper = shallow(<Todos />);
expect(wrapper).toMatchSnapshot();
});
it('should render a ul', () => {
const wrapper = shallow(<Todos />);
expect(wrapper.find('ul').length).toBe(1);
});
v1 Error:
...
Invariant Violation: could not find react-redux context value;
please ensure the component is wrapped in a <Provider>
...
Todos.test.js v2
...
// imported Provider from react-redux
it('renders without crashing', () => {
const wrapper = shallow(
<Provider store={store}>
<Todos />
</Provider>,
);
expect(wrapper).toMatchSnapshot();
});
it('should render a ul', () => {
const wrapper = shallow(<Provider store={store}><Todos /></Provider>);
expect(wrapper.find('ul').length).toBe(1);
});
v2 tests also fail since wrapper is the <Provider> and calling dive() on wrapper will return the same error as v1.
To mock useSelector use can do this
import * as redux from 'react-redux'
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })
I could test a component which uses redux hooks using enzyme mount facility and providing a mocked store to the Provider:
Component
import React from 'react';
import AppRouter from './Router'
import { useDispatch, useSelector } from 'react-redux'
import StartupActions from './Redux/Startup'
import Startup from './Components/Startup'
import './App.css';
// This is the main component, it includes the router which manages
// routing to different views.
// This is also the right place to declare components which should be
// displayed everywhere, i.e. sockets, services,...
function App () {
const dispatch = useDispatch()
const startupComplete = useSelector(state => state.startup.complete)
if (!startupComplete) {
setTimeout(() => dispatch(StartupActions.startup()), 1000)
}
return (
<div className="app">
{startupComplete ? <AppRouter /> : <Startup />}
</div>
);
}
export default App;
Test
import React from 'react';
import {Provider} from 'react-redux'
import { mount, shallow } from 'enzyme'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk';
import App from '../App';
const mockStore = configureMockStore([thunk]);
describe('App', () => {
it('should render a startup component if startup is not complete', () => {
const store = mockStore({
startup: { complete: false }
});
const wrapper = mount(
<Provider store={store}>
<App />
</Provider>
)
expect(wrapper.find('Startup').length).toEqual(1)
})
})
There is another way than #abidibo if you use a function selector defined in another file. You can mock useSelector and your selector function, and then use shallow from enzyme:
Component
import * as React from 'react';
import { useSelector } from 'react-redux';
import Spinner from './Spinner';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';
const Example = () => {
const isSpinnerDisplayed = useSelector(getIsSpinnerDisplayed);
return isSpinnerDisplayed ? <Spinner /> : <Button />;
};
export default Example;
Selectors
export const getIsSpinnerDisplayed = state => state.isSpinnerDisplayed;
Test
import * as React from 'react';
import { shallow } from 'enzyme';
import Example from './Example';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
jest.mock('./selectors');
describe('Example', () => {
it('should render Button if getIsSpinnerDisplayed returns false', () => {
getIsSpinnerDisplayed.mockReturnValue(false);
const wrapper = shallow(<Example />);
expect(wrapper.find(Button).exists()).toBe(true);
});
});
It may be a little bit hacky, but it works well for me :)
Testing React Redux Hooks With Enzyme's Shallow Rendering
After reading through all the responses here and digging through the documentation, I wanted to aggregate the ways to test React components using react-redux hooks with Enzyme and shallow rendering.
These tests rely on mocking the useSelector and useDispatch hooks. I'll also provide examples in both Jest and Sinon.
Basic Jest Example
import React from 'react';
import { shallow } from 'enzyme';
import * as redux from 'react-redux';
import TodoList from './TodoList';
describe('TodoList', () => {
let spyOnUseSelector;
let spyOnUseDispatch;
let mockDispatch;
beforeEach(() => {
// Mock useSelector hook
spyOnUseSelector = jest.spyOn(redux, 'useSelector');
spyOnUseSelector.mockReturnValue([{ id: 1, text: 'Old Item' }]);
// Mock useDispatch hook
spyOnUseDispatch = jest.spyOn(redux, 'useDispatch');
// Mock dispatch function returned from useDispatch
mockDispatch = jest.fn();
spyOnUseDispatch.mockReturnValue(mockDispatch);
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should render', () => {
const wrapper = shallow(<TodoList />);
expect(wrapper.exists()).toBe(true);
});
it('should add a new todo item', () => {
const wrapper = shallow(<TodoList />);
// Logic to dispatch 'todoAdded' action
expect(mockDispatch.mock.calls[0][0]).toEqual({
type: 'todoAdded',
payload: 'New Item'
});
});
});
Basic Sinon Example
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import * as redux from 'react-redux';
import TodoList from './TodoList';
describe('TodoList', () => {
let useSelectorStub;
let useDispatchStub;
let dispatchSpy;
beforeEach(() => {
// Mock useSelector hook
useSelectorStub = sinon.stub(redux, 'useSelector');
useSelectorStub.returns([{ id: 1, text: 'Old Item' }]);
// Mock useDispatch hook
useDispatchStub = sinon.stub(redux, 'useDispatch');
// Mock dispatch function returned from useDispatch
dispatchSpy = sinon.spy();
useDispatchStub.returns(dispatchSpy);
});
afterEach(() => {
sinon.restore();
});
// More testing logic...
});
Testing Multiple useSelector Hooks
Testing multiple useSelectors requires us to mock the Redux app state.
var mockState = {
todos: [{ id: 1, text: 'Old Item' }]
};
Then we can mock our own implementation of useSelector.
// Jest
const spyOnUseSelector = jest.spyOn(redux, 'useSelector').mockImplementation(cb => cb(mockState));
// Sinon
const useSelectorStub = sinon.stub(redux, 'useSelector').callsFake(cb => cb(mockState));
I think this is both the best and the simplest way to mock useSelector hook from Redux store in jest:
import * as redux from 'react-redux'
const user = {
id: 1,
name: 'User',
}
const state = { user }
jest
.spyOn(redux, 'useSelector')
.mockImplementation((callback) => callback(state))
With the idea being that you are able to provide the state store mock with just a subset of store data.
After searching for help I combined some of the methods I found to mock useSelector.
First create a function that does some bootstrapping before your test.
Setting up the store with some values that you want to overwrite and mock the useSelector function of react-redux.
I think it is really useful for creating multiple testcases where u see how the store state influences the behaviour of your component.
import configureMockStore from 'redux-mock-store';
import * as Redux from 'react-redux';
import MyComponent from './MyComponent';
const mockSelectors = (storeValues) => {
const mockStore = configureMockStore()({
mobile: {
isUserConnected: false
...storeValues
},
});
jest
.spyOn(Redux, 'useSelector')
.mockImplementation(state => state.dependencies[0](mockStore.getState()));
};
describe('isUserConnected: true', () => {
beforeEach(() => {
mockSelectors({ isUserConnected: true });
component = shallow(<MyComponent />);
test('should render a disconnect button', () => {
expect(component).toBeDefined();
expect(component.find('button')).toBeTruthy();
});
});
});
And the component:
import React from 'react';
import { shallowEqual, useSelector } from 'react-redux';
const MyComponent = () => {
const isConnected = useSelector(selectIsConnected, shallowEqual);
return (
<>
{
showDisconnect ? (
<button type="button" onClick={() => ()}>disconnect</button>
) : null
}
</>
);
};
export default MyComponent;
Below code works for me.
import { configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ServiceListingScreen from './ServiceListingScreen';
import renderer from 'react-test-renderer';
import { createStore } from 'redux';
import serviceReducer from './../store/reducers/services';
import { Provider } from 'react-redux'
const store = createStore(serviceReducer ) ;
configure({adapter: new Adapter()});
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store={reduxStore}>{children}</Provider>
)
describe('Screen/ServiceListingScreen', () => {
it('renders correctly ', () => {
const wrapper = shallow(<Provider store={store}><ServiceListingScreen /></Provider>);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
You can try redux-saga-test-plan cool redux assertion and running library and lightweight it doest all your heavy lifting of running saga and assertion automatically
const mockState = { rick:"Genius", morty:"dumbAsss"}
expectSaga(yourCoolSaga)
.provide({
select({ selector }, next) {
if (selector) {
return mockState;
}
return next();
}
})
// some assertion here
.put(actions.updateRickMortyPowers(mockState))
.silentRun();
Some of the answers above mocked the useSelector or useDispatch but the redux docs actually advice against that...check out this answer here: https://stackoverflow.com/a/74024854/14432913 which worked for me and followed the example in redux docs
this is worked for me as well :
import { shallow, mount } from "enzyme";
const store = mockStore({
startup: { complete: false }
});
describe("==== Testing App ======", () => {
const setUpFn = props => {
return mount(
<Provider store={store}>
<App />
</Provider>
);
};
let wrapper;
beforeEach(() => {
wrapper = setUpFn();
});

Resources