I am attempting to test that I am calling one of my mapDispatchToProps functions with args, however I can't seem to get it to work...
I attempted to follow this previous question, but it didn't seem to work for me.
Component.jsx
const mapDispatchToProps = dispatch => ({
myFunction: (data) => dispatch(myAction(data))
});
const Component = ({buttonText, myFunction}) => (
<button data-testid="test" onClick={() => myFunction(123)}>{buttonText}</button>
)
export default connect(null, mapDispatchToProps)(Component);
Actions.js
export const myAction = agentData => ({
type: `MY_ACTION`,
agentData
});
Test.js
import createMockStore from "redux-mock-store";
it('Should pass', () => {
const mockStore = createMockStore();
const store = mockStore({});
const mockUpdate = jest.fn(data => console.log('HIT FUNCTION with ' + data));
const props = {buttonText: 'Click me', myFunction: mockUpdate};
render(
<Provider store={store}>
<Component {...props}/>
</Provider>
);
userEvent.click(screen.queryByTestId('test'));
expect(mockUpdate).toHaveBeenCalled();
expect(mockUpdate).toHaveBeenCalledWith(123);
});
I have also tried moving the myFunction: mockUpdate from the props object into the mockStore({}) initial object, but still no luck...
Your mock myFunction didn't invoke when you click the button. Because the returned value of mapDispatchToProps will become the props of the component.
You can get the mock myFunction via the second parameter of mapDispatchToProps function named ownProps. You may want to call the mock myFunction when dispatching the action. If so, expect(mockUpdate).toHaveBeenCalled(); assertion will work.
component.tsx:
import React from 'react';
import { connect } from 'react-redux';
import { myAction } from './actions';
const mapDispatchToProps = (dispatch, ownProps) => {
console.log('Your mock myFunction is here:', ownProps.myFunction);
// But below myFunction is not a mock function and is passed into your component finally.
return {
myFunction: (data) => dispatch(myAction(data)),
};
};
const Component = ({ buttonText, myFunction }) => (
<button data-testid="test" onClick={() => myFunction(123)}>
{buttonText}
</button>
);
export default connect(null, mapDispatchToProps)(Component);
import React from 'react';
import { render, screen } from '#testing-library/react';
import { Provider } from 'react-redux';
import createMockStore from 'redux-mock-store';
import userEvent from '#testing-library/user-event';
import Component from './component';
it('Should pass', async () => {
const mockStore = createMockStore();
const store = mockStore();
render(
<Provider store={store}>
<Component buttonText="Click me" />
</Provider>
);
await userEvent.click(screen.getByTestId('test'));
expect(store.getActions()).toEqual([{ type: 'MY_ACTION', agentData: 123 }]);
});
Test result:
PASS stackoverflow/75173986/component.test.tsx (8.625 s)
✓ Should pass (92 ms)
console.log
Your mock myFunction is here: [Function: mockConstructor] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
mock: [Getter/Setter],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)],
mockReturnValue: [Function (anonymous)],
mockResolvedValue: [Function (anonymous)],
mockRejectedValue: [Function (anonymous)],
mockImplementationOnce: [Function (anonymous)],
mockImplementation: [Function (anonymous)],
mockReturnThis: [Function (anonymous)],
mockName: [Function (anonymous)],
getMockName: [Function (anonymous)]
}
at Function.mapDispatchToProps [as mapToProps] (stackoverflow/75173986/component.tsx:6:11)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
actions.ts | 100 | 100 | 100 | 100 |
component.tsx | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.103 s, estimated 10 s
Ran all test suites related to changed files.
I found a solution by combining something similar to #Lin Du and also some other resources I found online (that I can't seem to find in my search history to link to at the moment).
This solution doesn't require any additional args such as testProps to be added to the mapDispatchToProps function.
import configureStore from 'redux-mock-store';
...
it('Should call next step with both call plan and caller display', () => {
const store = { /* any mapStateToProps data here */ };
const mockStore = configureStore()(store);
mockStore.dispatch = jest.fn(); // Can add callback in here to update the non-mock store
const props = {buttonText: 'Click me'};
provider = getComponentProvider(ChooseCallPlan, store, {wizard}, true);
render(
<Provider store={mockStore}>
<Component {...props}/>
</Provider>
);
userEvent.click(screen.queryByTestId('test'));
export(mockStore.dispatch).toHaveBeenCalled();
expect(mockStore.dispatch)
.toHaveBeenCalledWith(myAction(123));
});
Related
I am using mapDispatchToProps to make a dispatch call to an API in the useEffect of my functional component.
I am a little stuck as how to actually test this in my unit tests with React Testing Library.
I can pass my Redux store quite easily, however I don't think I've ever had to pass the dispatch before and I'm a little lost.
I did try to pass the dispatch function with my store, but this of course didn't work.
Component
const mapDispatchToProps = dispatch => ({
myDispatchFunction: () => dispatch(someDispatch())
});
const mapStateToProps = ({someStateProp}) => ({
myStateProp: !!someStateProp // This isn't important
});
const MyComp = ({myDispatchFunction}) => {
useEffect(() => {
!!myStateProp && myDispatchFunction();
}, []);
return ...
}
Test
it('Should trigger dispatch function on load', () => {
const mockFunc = jest.fn(); // My attempt at mocking the dispatch call
const store = {someStateProp: true, myDispatchFunction: mockFunc};
render(
<Provider store={mockStore(store)}>
<MyComponent />
</Provider>
);
expect(mockFunc).toHaveBeenCalled();
});
This fails...
Your code has no real meaning, but the same with the test method, try not to mock functions and modules that have no side effects, I/O operations, and use their original implementation.
For your example, don't mock mapStateToProps, mapDispatchToProps, and dispatch functions. We can create a test store, populate the test state data, and collect the dispatch actions in the component.
If your component uses some state slice, verify that your component is rendering correctly. This is also called behavior testing. This testing strategy is more robust if the component behaves correctly, regardless of whether your implementation changes.
Why not mock? Like mapStateToProps, where a mock implementation changes its behavior if you don't know how it is implemented, resulting in an error-based implementation of the test case. Your tests may pass but the actual code at runtime is not correct.
E.g.
index.tsx:
import { useEffect } from 'react';
import { connect } from 'react-redux';
const mapDispatchToProps = (dispatch) => ({
myDispatchFunction: () => dispatch({ type: 'SOME_ACTION' }),
});
const mapStateToProps = ({ someStateProp }) => ({
myStateProp: !!someStateProp,
});
const MyComp = ({ myDispatchFunction, myStateProp }) => {
useEffect(() => {
!!myStateProp && myDispatchFunction();
}, []);
return null;
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComp);
index.test.tsx:
import { render } from '#testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import MyComp from './';
describe('71555438', () => {
test('should pass', () => {
let dispatchedActions: any[] = [];
const store = createStore(function rootReducer(state = { someStateProp: 'fake value' }, action) {
if (!action.type.startsWith('##redux')) {
dispatchedActions.push(action);
}
return state;
});
render(
<Provider store={store}>
<MyComp />
</Provider>
);
expect(dispatchedActions).toEqual([{ type: 'SOME_ACTION' }]);
});
});
Test result:
PASS stackoverflow/71555438/index.test.tsx (9.562 s)
71555438
✓ should pass (16 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.tsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.565 s
Note: We should ignore the action dispatched by createStore internally and we only need to collect the actions dispatched by users(our code).
I am new to writing test cases for react. What am I doing wrong in the below code?
My component
// Dummy.js
import React, {Component} from "react";
import axios from "axios";
export default class Dummy extends Component {
state = {
name: "",
error: false,
data : []
};
componentDidMount() {
this.getData();
}
getData = () => {
axios
.get("https://reqres.in/api/users?page=2")
.then((response) => {
this.setState({
name : response.data.data[0].first_name,
data : response.data.data
});
return "Success"
})
.catch(() => {
this.setState({
error: true,
});
});
};
render() {
return <div>
<h1 data-testid ="test">{this.state.name}</h1>
</div>;
}
}
My test case
// dummy.test.js
import React from "react";
import {shallow} from "enzyme";
import Dummy from "../Dummy";
import axios from "axios";
jest.mock("axios");
const data = {
page: 2,
per_page: 6,
total: 12,
total_pages: 2,
data: [
{
id: 7,
email: "michael.lawson#reqres.in",
first_name: "Michael",
last_name: "Lawson",
avatar: "https://reqres.in/img/faces/7-image.jpg",
},
{
id: 8,
email: "lindsay.ferguson#reqres.in",
first_name: "Lindsay",
last_name: "Ferguson",
avatar: "https://reqres.in/img/faces/8-image.jpg",
},
],
};
test("should fetch users", () => {
const wrapper = shallow(<Dummy />);
const resp = {data: data};
axios.get.mockResolvedValue(resp);
wrapper
.instance()
.getData()
.then((resp) => {
console.log(resp);
expect(wrapper.state("data")).toEqual(resp);
});
});
Below is the error I am getting when trying to execute my test case.
FAIL src/Dummy/__tests__/dummy.test.js
× should fetch users (5 ms)
● should fetch users
TypeError: Cannot read property 'then' of undefined
12 | }
13 | getData = () => {
> 14 | axios
| ^
15 | .get("https://reqres.in/api/users?page=2")
16 | .then((response) => {
17 | this.setState({
at Dummy.getData (src/Dummy/Dummy.js:14:5)
at Dummy.componentDidMount (src/Dummy/Dummy.js:11:10)
at fn (node_modules/enzyme/src/ShallowWrapper.js:429:22)
at Object.batchedUpdates (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:807:16)
at new ShallowWrapper (node_modules/enzyme/src/ShallowWrapper.js:428:26)
at shallow (node_modules/enzyme/src/shallow.js:10:10)
at Object.<anonymous> (src/Dummy/__tests__/dummy.test.js:32:19)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 3.862 s
Ran all test suites related to changed files.
Don't know much about jest and enzyme. Please suggest functions/code which helps me on solving my problem.
mockResolvedValue is a Jest function that should be called like so:
axios.get = jest.fn().mockResolvedValue(mockedUsers);
The rest of the test should be unchanged.
When you directly mock any module or library, it's exports are replaced with undefined values. To be able to correctly mock axios and make it useful in your test-case, take a look at this package: https://www.npmjs.com/package/axios-mock-adapter
Also, depends on the enzyme version, in newest versions shallow is triggering componentDidMount lifecycle, so you need to mock the HTTP client before shallow-rendering - https://enzymejs.github.io/enzyme/docs/api/shallow.html
Please follow below steps:
Create the mockaxios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} }))
};
Inside your dummy.test.js mock it with jest
import axios from 'axios'
import mockAxios from './mockaxios.js'
jest.mock('axios', () => ({
__esModule: true,
default: mockAxios
}));
And finally use it as
mockAxios.get.mockImplementationOnce(() => Promise.resolve(resp));
So I was trying to create a unit test using jest in ReactJS. The Unit test itself just to verify if the function (from action) has been called
I already tried to mock the function, but the result tells that I must mock the function
Here the code of the function that I want to create a unit test
import { testfunction } from '../../actions/auth';
handleSubmit(userParams) {
this.setState({ form: { ...this.state.form, isFetching: true } });
this.props.dispatch(testfunction(userParams,
this.successCallback.bind(this), this.errorCallback.bind(this)));
}
and for the unit test
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import Login from '../../../components/auth/Login';
const mockStore = configureStore([thunk]);
const initialState = {
history: { },
};
const store = mockStore(initialState);
let wrapper;
let history;
let testfunction;
beforeEach(() => {
testfunction= jest.fn();
history = { push: jest.fn() };
wrapper = shallow(
<Login
history={history}
store={store}
testfunction={testfunction}
/>
);
});
describe('handleSubmit()', () => {
test('should call testfunction props', () => {
const component = wrapper.dive();
const instance = component.instance();
const sampleUserParams = {
email: 'test#test.com',
password: 'samplePassword123',
};
instance.handleSubmit(sampleUserParams);
expect(testfunction).toHaveBeenCalled();
});
});
I just want to check if the "testfunction" is called when I called handleSubmit function. But the error message is:
"Expected mock function to have been called."
it feels my way to mock the function is wrong. Does anyone know how to correct way to test that function?
Here is the solution:
index.tsx:
import React, { Component } from 'react';
import { testfunction } from './testfunction';
class Login extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
form: {}
};
}
public render() {
const userParams = {};
return (
<div className="login">
<form onSubmit={() => this.handleSubmit(userParams)}>some form</form>
</div>
);
}
private handleSubmit(userParams) {
this.setState({ form: { ...this.state.form, isFetching: true } });
this.props.dispatch(testfunction(userParams, this.successCallback.bind(this), this.errorCallback.bind(this)));
}
private successCallback() {
console.log('successCallback');
}
private errorCallback() {
console.log('errorCallback');
}
}
export { Login };
testFunction.ts:
async function testfunction(userParams, successCallback, errorCallback) {
return {
type: 'ACTION_TYPE',
payload: {}
};
}
export { testfunction };
Unit test:
import React from 'react';
import { shallow } from 'enzyme';
import { Login } from './';
import { testfunction } from './testfunction';
jest.mock('./testfunction.ts');
describe('Login', () => {
const dispatch = jest.fn();
const sampleUserParams = {
email: 'test#test.com',
password: 'samplePassword123'
};
it('handleSubmit', () => {
const wrapper = shallow(<Login dispatch={dispatch} />);
expect(wrapper.is('.login')).toBeTruthy();
expect(wrapper.find('form')).toHaveLength(1);
wrapper.find('form').simulate('submit');
const cmpInstance = wrapper.instance();
expect(dispatch).toBeCalledWith(
// tslint:disable-next-line: no-string-literal
testfunction(sampleUserParams, cmpInstance['successCallback'], cmpInstance['errorCallback'])
);
// tslint:disable-next-line: no-string-literal
expect(testfunction).toBeCalledWith(sampleUserParams, cmpInstance['successCallback'], cmpInstance['errorCallback']);
});
});
Unit test with coverage report:
PASS src/stackoverflow/57847401/index.spec.tsx
Login
✓ handleSubmit (22ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 86.36 | 100 | 62.5 | 85 | |
index.tsx | 90 | 100 | 71.43 | 88.89 | 27,30 |
testfunction.ts | 50 | 100 | 0 | 50 | 2 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.201s, estimated 4s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57847401
Wondering if someone can point out what I expect is a stupid mistake.
I have an action for user login.
I'm trying to test this action, I've followed the redux documentation as well as the redux-mock-store documentation however I keep getting an error as follows:
TypeError: Cannot read property 'default' of undefined
4 | import thunkMiddleware from 'redux-thunk'
5 |
> 6 | const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
| ^
7 | const mockStore = configureStore(middlewares)
8 |
9 | describe("userActions", () => {
at Object.thunkMiddleware (actions/user.actions.spec.js:6:22)
My test code is as follows:
import {userActions} from "./user.actions";
import {userConstants} from "../constants/user.constants";
import configureStore from 'redux-mock-store'
import thunkMiddleware from 'redux-thunk'
const middlewares = [thunkMiddleware] // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares)
describe("userActions", () => {
describe("login", () => {
it(`should dispatch a ${userConstants.LOGIN_REQUEST}`, () =>{
const store = mockStore({});
return store.dispatch(userActions.login("someuser", "somepassword")).then(() => {
expect(store.getState().loggingIn).toBeTruthy();
});
})
})
});
I've double checked both redux-thunk and redux-mock-store are included in my npm dev dependencies as well as deleteing the node_modules directory and reinstalling them all with npm install.
Can anyone see what's going wrong?
Thanks
EDIT:
It seems i'm doing something fundamentally wrong, I've tried to simplify it almost back to a clean slate to find where the problem is introduced.
Even with this test:
import authentication from "./authentication.reducer";
import { userConstants } from "../constants/user.constants";
describe("authentication reducer", () => {
it("is a passing test", () => {
authentication();
expect("").toEqual("");
});
});
Against this:
function authentication(){
return "test";
}
export default authentication
I'm getting an undefined error:
● authentication reducer › is a passing test
TypeError: Cannot read property 'default' of undefined
6 |
7 | it("is a passing test", () => {
> 8 | authentication();
| ^
9 | expect("").toEqual("");
10 | });
at Object.<anonymous> (reducers/authentication.reducer.spec.js:8:9)
yes, according to that error, seems you have a problem with module dependencies. Take a look at your webpack configuration.
Concerning the redux-mock-store, I suggest you to create a helper for future testing needs:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
export default function(middlewares = [thunk], data = {}) {
const mockedStore = configureStore(middlewares)
return mockedStore(data)
}
and you will include it in your test cases and use like that:
beforeEach(() => {
store = getMockStore()
})
afterEach(() => {
store.clearActions()
})
If you wont test redux with thunk you can use redux-thunk-tester module for it.
Example:
import React from 'react';
import {createStore, applyMiddleware, combineReducers} from 'redux';
import {asyncThunkWithRequest, reducer} from './example';
import ReduxThunkTester from 'redux-thunk-tester';
import thunk from 'redux-thunk';
const createMockStore = () => {
const reduxThunkTester = new ReduxThunkTester();
const store = createStore(
combineReducers({exampleSimple: reducer}),
applyMiddleware(
reduxThunkTester.createReduxThunkHistoryMiddleware(),
thunk
),
);
return {reduxThunkTester, store};
};
describe('Simple example.', () => {
test('Success request.', async () => {
const {store, reduxThunkTester: {getActionHistoryAsync, getActionHistoryStringifyAsync}} = createMockStore();
store.dispatch(asyncThunkWithRequest());
const actionHistory = await getActionHistoryAsync(); // need to wait async thunk (all inner dispatch)
expect(actionHistory).toEqual([
{type: 'TOGGLE_LOADING', payload: true},
{type: 'SOME_BACKEND_REQUEST', payload: 'success response'},
{type: 'TOGGLE_LOADING', payload: false},
]);
expect(store.getState().exampleSimple).toEqual({
loading: false,
result: 'success response'
});
console.log(await getActionHistoryStringifyAsync({withColor: true}));
});
});
I have the following component wrapped in react-i18next and a mobx-react provider:
import React from "react";
import s from "styled-components";
import { observer, inject } from "mobx-react";
import { translate } from "react-i18next";
#inject("uiStore", "fieldsStore")
#observer
export class HeaderCell extends React.Component {
render() {
const { t } = this.props;
return (
<InputSyle>
<div className="rootCell" />
</InputSyle>
);
}
}
const InputSyle = s.tr`
background-color: red;
`;
export default translate()(HeaderCell);
I am trying to write a simplet test to assert that the .rootCell div gets rendered:
import React from "react";
import { Provider } from "mobx-react";
import Enzyme from "enzyme";
import { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-15";
import HeaderCell from "./HeaderCell";
import { I18nextProvider } from "react-i18next";
import i18n from "../../../../utils/i18n";
const stores = {
uiStore: {},
fieldsStore: {}
};
Enzyme.configure({ adapter: new Adapter() });
describe("HeaderCell.js", () => {
it("renders a single .rootCell div", () => {
const wrapper = mount(
<I18nextProvider i18n={i18n}>
<Provider {...stores}>
<HeaderCell />
</Provider>
</I18nextProvider>
);
expect(wrapper.find(".rootCell")).toHaveLength(1);
});
});
But instead of the test passing I get the following console output:
FAIL
src/components/Sidebar/ParcelsGrid/HeaderCell/HeaderCell.test.js ●
HeaderCell.js › renders a single .rootCell div
expect(received).toHaveLength(length)
Expected value to have length:
1
Received:
{"length": 0, Symbol(enzyme.__unrendered__): null, Symbol(enzyme.__renderer__): {"batchedUpdates": [Function
batchedUpdates], "getNode": [Function getNode], "render": [Function
render], "simulateEvent": [Function simulateEvent], "unmount":
[Function unmount]}, Symbol(enzyme.root): {"length": 1,
Symbol(enzyme.unrendered): , Symbol(enzyme.renderer):
[Object], Symbol(enzyme.root): [Circular],
Symbol(enzyme.node): [Object], Symbol(enzyme.nodes): [Array],
Symbol(enzyme.options): [Object]}, Symbol(enzyme.node):
undefined, Symbol(enzyme.nodes): Array [],
Symbol(enzyme.options): {"adapter": [Object]}}
received.length:
0
at Object.it (src/components/Sidebar/ParcelsGrid/HeaderCell/HeaderCell.test.js:30:39)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
From the message I am assuming that my component is not getting rendered, but I have no idea why.
I am using "custom-react-scripts": "^0.2.0", "enzyme": "^3.2.0",
"enzyme-adapter-react-15": "^1.0.5", "react-i18next": "^6.0.6".
I would say that the test code is correct, but not really sure? All the wrappers also seem to be working OK as there are no console errors.