I have the following component in my react redux project and I'm trying to implement tests but running into problems when trying to test the components
Users.js
export class Users extends Component {
componentWillMount() {
const dispatch = this.context.store.dispatch;
dispatch({ type: UserActions.fetchUsers(dispatch)});
}
render() {
const {users,isFetching} = this.props;
return (
<div>
<CardHeader
title={this.props.route.header}
style={{backgroundColor:'#F6F6F6'}}/>
<UserListHeader/>
{isFetching ? <LinearProgress mode="indeterminate" /> : <UserList users={users}/>}
</div>
)
}
}
Users.propTypes = {
users: PropTypes.array,
actions: PropTypes.object.isRequired
};
Users.contextTypes = {
store: React.PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
users: state.users.users,
isFetching:state.users.isFetching
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(UserActions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Users);
and I'm trying to test it like the example on the Redux website, but I'm running into issues
UsersSpec.js
import {Users} from '/containers/users/Users'
const middlewares = [ thunk ];
const mockStore = configureMockStore(middlewares);
function usersSetup() {
const props = {
users: expect.createSpy(),
isFetching:expect.createSpy()
};
const enzymeWrapper = mount(<Users />,{context:{store:mockStore}});
return {
props,
enzymeWrapper
}
}
describe('Users', () => {
it('should render self and subcomponents', () => {
const { enzymeWrapper } = usersSetup();
exp(enzymeWrapper.find(UserListHeader)).to.have.length(1);
})
})
But I get the error 'TypeError: dispatch is not a function' should I be mocking the componentWillMount function or how should I test this component.
Should I just be testing the dumb child components? Any guidance would be great. Thanks
mount function provides full dom rendering therefore you'll need to set up jsdom in your test setup. You can see more info here:
Error: It looks like you called `mount()` without a global document being loaded
Another thing is that, you should provide childContextTypes attribute when you're defining context with mount like this:
mount(<Users />, {
context: { store: mockStore },
childContextTypes: { store: PropTypes.object }
});
However if you're writing unit test of a React component you should use shallow function provided by enzyme. It just renders the component with one deep level, so that you don't need to create jsdom and provide context parameters when you're creating the wrapper component. mount rendering in a test means you're actually writing an integration test.
const wrapper = shallow(<Users />);
wrapper.find(UserListMenu).to.have.length(1);
Related
I implemented load on demand logic in a react/redux application page by invoking a page container mapDispatchToProps method in the page component's componentDidMount method.
The load on demand logic consists of these lines in the page component and its container:
import { isEmpty, isNil } from 'lodash';
import {SimplActions} from 'simpl-react/lib/actions';
class LoadOnDemandPage extends React.Component {
componentDidMount() {
const {run, loadedRun, loadRunData} = this.props;
loadRunData(run, loadedRun);
}
.....
}
LoadOnDemandPage.propTypes = {
run: PropTypes.object.isRequired,
loadedRun: PropTypes.object,
loadRunData: PropTypes.func.isRequired,
};
function mapStateToProps(state, ownProps) {
...
return {
run,
loadedRun: state.simpl.loaded_run,
};
}
const mapDispatchToProps = dispatch => {
return {
loadRunData(run, loadedRun) {
if (!isNil(run)) {
if (isEmpty(loadedRun) || run.pk != loadedRun.pk) {
// load run's world data
const runId = run.id;
const topic = `model:model.run.${runId}`;
dispatch(SimplActions.getDataTree(topic));
dispatch(SimplActions.setLoadedRun(runId));
}
}
},
};
};
const module = connect(
mapStateToProps,
mapDispatchToProps
)(LoadOnDemandPage);
export default withRouter(module);
Now, I'd like to use the same mapDispatchToProps loadRunData method in other pages and other applications by moving it into an external library.
Can this logic be made reusable or must it be replicated in every page component that uses this pattern? FWIW I'm using react v16 and redux v3.
you could create a currying function that takes dispatch, saving in some utils folder:
// above import dependencies
const loadRunDataBuilder = dispatch => (run, loadedRun) {
if (!isNil(run)) {
if (isEmpty(loadedRun) || run.pk != loadedRun.pk) {
// load run's world data
const runId = run.id;
const topic = `model:model.run.${runId}`;
dispatch(SimplActions.getDataTree(topic));
dispatch(SimplActions.setLoadedRun(runId));
}
}
},
now you could import that function at any component and call it passing dispatch
const mapDispatchToProps = dispatch => {
return {
loadRunData: loadRunDataBuilder(dispatch)
},
};
I'm working on a unit test in a React application that verifies a passed in prop function is being conditionally called based on another props value. I'm utilizing Typescript/Enzyme/Jest in my application and am using a Root wrapper around the component I'm testing to inject the Redux store (and override initial state if desired).
import { mount, ReactWrapper } from "enzyme";
import React from "react";
import Login from "src/components/auth/Login";
import Root from "src/Root";
let wrapped: ReactWrapper;
let defaultProps = {
signIn: jest.fn(),
token: null,
};
beforeEach(() => {
wrapped = mount(
<Root>
<Login {...defaultProps} />
</Root>
);
});
describe("on mount", () => {
describe("if no token is supplied to the props", () => {
it("will call the props.signIn() function", () => {
expect(defaultProps.signIn).toHaveBeenCalled();
});
});
});
When I run the test the toHaveBeenCalled() (as well as toBeCalled(), tried both) are not registering any calls. However, I have supplied a console.log statement that is getting triggered within the same conditional that the signIn() function lives.
import React from 'react';
import { AuthState, JWT } from "src/components/auth/types";
import { signIn } from "src/redux/auth";
interface Props {
signIn: () => Promise<void>;
token: null | JWT;
}
class Login extends React.Component<Props> {
/**
* Sign the user in on mount
*/
public componentDidMount(): void {
if (!this.props.token) {
console.log("GETTING HERE");
this.props.signIn();
}
}
public render(): JSX.Elemeent {
// ... More code
}
}
const mapStateToProps = (state: AuthState) => {
return {
token: state.auth.token;
};
};
export default connect(mapStateToProps, { signIn })(Login);
I've gone through several related posts/articles but all of the different configurations, such as traversing enzyme to get the direct prop or utilizing spyOn, have failed.
The only thing I can figure that's different is my wrapping of the Login component with Root, but considering I can see the console.log being triggered this seems like a complete shot in the dark.
Can anyone tell me what I'm doing wrong here?
You've to wait for component to mount, so:
it("will call the props.signIn() function", (done) => {
setImmediate(() => {
expect(defaultProps.signIn).toHaveBeenCalled();
done()
});
});
Ended up being me forgetting to place the override via mapDispatchToProps and mapStateToProps in the connect function. This was causing my passed in signIn function to be overridden by the signIn action imported in the file. Updating with ownProps and conditionally utilizing the passed in value fixes the issue:
const mapStateToProps = (state: AuthState, ownProps: Props) => {
return {
token: ownProps.token || state.auth.token;
};
};
const mapDispatchToProps = (dispatch: ThunkDispatch<{}, {}, any>, ownProps: Props) => {
return {
signIn: ownProps.signIn || (() => { return dispatch(signIn()) })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
I'm using a method added to the context which is triggered in the componentDidMount() lifecycle method.
I should be able to stub the context by providing an option to Enzyme's shallow() method, but this does not get passed to my component. For example:
My test:
it('renders without crashing', () => {
const context = { dispatch: jest.fn() };
shallow(<MyComponent />, { context });
});
and my component:
import React, { Component } from 'react';
import { Consumer, Context } from '../../context';
class MyComponent extends Component {
static contextType = Context;
componentDidMount() {
const { dispatch } = this.context; // dispatch is `undefined`
dispatch({ type: 'BLAH', payload: 'blah' });
}
etc...
}
this.context is an object, but it has no properties - dispatch is always undefined.
Unfortunately, enzyme doesn't support createContext and contextType yet.
You can see its progress here.
This is quite hacky, but you can use existing support for the legacy API to test components that actually use the new API.
In your case, you can do the following in your test code:
import PropTypes from 'prop-types';
MyComponent.contextTypes = {
dispatch: PropTypes.any,
};
Now shallow(<MyComponent />, { context }); should shallow render with your desired context value.
You can use module shallow-with-context than enzyme will support contextType.
Then your test can look like:
import { shallow } from 'enzyme';
import { withContext, createContext } from 'shallow-with-context';
it('renders without crashing', () => {
const context = createContext({ dispatch: jest.fn() });
const MyComponentWithContext = withContext(MyComponent, context);
shallow(<MyComponent />, { context });
});
I want to test that function passed from mapDispatchToProps was invoked when button clicking is simulated.
How to test that function which passed from mapDispatchToProps is invoked?
I tried to pass a mocked function by props, but it doesn't work. Any help will be appreciated.
Here below my fake class code and test example.
My component
// All required imports
class App extends React.Component<Props> {
render() {
const { onClick } = this.props;
return (
<>
<h1>Form</h1>
<input />
<button onClick={() => onClick()} />
</>
);
}
}
const mapStateToProps = (state) => {
return {
state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onClick: () => dispatch(actions.onClick())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
My test file
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16/build/index';
import jsdom from 'jsdom';
import React from 'react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import ConnectedApp, { App } from './App';
function setUpDomEnvironment() {
const { JSDOM } = jsdom;
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost/' });
const { window } = dom;
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
}
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
setUpDomEnvironment();
configure({ adapter: new Adapter() });
const mockStore = configureMockStore();
describe('App', () => {
describe('When App connected to store', () => {
describe('When button clicked', () => {
it('should not crush after click on login button', () => {
const onClick = jest.fn()
const store = mockStore(initialStates[1]);
const wrapper = mount(
<Provider store={store}>
<ConnectedApp />
</Provider>);
wrapper.find('button').simulate('click');
??? how to test that function passed from mapDispatchToProps was fired?
});
});
});
});
I recommend following the approach described in the docs and export the connected component as the default export for use in the application, and export the component itself as a named export for testing.
For the code above export the App class and test the click like this:
import * as React from 'react';
import { shallow } from 'enzyme';
import { App } from './code';
describe('App', () => {
it('should call props.onClick() when button is clicked', () => {
const onClick = jest.fn();
const wrapper = shallow(<App onClick={onClick} />);
wrapper.find('button').simulate('click');
expect(onClick).toHaveBeenCalledTimes(1);
});
});
shallow provides everything that is needed for testing the component itself. (shallow even calls React lifecycle methods as of Enzyme v3)
As you have found, to do a full rendering of the component requires a mock redux store and wrapping the component in a Provider. Besides adding a lot of complexity, this approach also ends up testing the mock store and all child components during the component unit tests.
I have found it much more effective to directly test the component, and to export and directly test mapStateToProps() and mapDispatchToProps() which is very easy since they should be pure functions.
The mapDispatchToProps() in the code above can be tested like this:
describe('mapDispatchToProps', () => {
it('should dispatch actions.onClick() when onClick() is called', () => {
const dispatch = jest.fn();
const props = mapDispatchToProps(dispatch);
props.onClick();
expect(dispatch).toHaveBeenCalledWith(actions.onClick());
});
});
This approach makes unit testing the component very simple since you can pass the component props directly, and makes it very simple to test that the component will be handed the correct props by the pure functions (or objects) passed to connect().
This ensures that the unit tests are simple and targeted. Testing that connect() and redux are working properly with the component and all of its child components in a full DOM rendering can be done in the e2e tests.
I'm having a really hard time learning how to use jest. All the tutorials i come across either teach you how to test a script that renders to dom such as <App /> with or without snapshots. Other tutorials goes over how to mock tests with inputs. but I cant seem to find tutorials that explains clearly and give examples that i can use.
For example the script from below i have an idea on how to test the render portion, but i don't know how to test the redux or the rest of the functions.
Could anyone give an example of how to test the below script that i can use as a reference for the rest of the files i need to test in my project?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import CustomSearch from '../Components/CustomSearch';
import CustomToolBar from '../Components/CustomToolBar';
import Table from '../Components/Table';
import InsertButton from '../Components/InsertButton';
import UserForm from './UserForm ';
import { fetchUsers, deleteUser } from '../../actions/users';
import setModal from '../../actions/modal';
import TableColumns from '../../constants/data/TableColumns';
class Users extends Component {
constructor(props) {
super(props);
this.onInsert = this.onInsert.bind(this);
this.onDelete = this.onDelete.bind(this);
this.onEdit = this.onEdit.bind(this);
this.props.fetchUsers({ accountId: this.props.userData.account.id, token: props.userData.token });
}
onDelete(row) {
if (confirm(`Are you sure you want to delete user ${row.first} ${row.last}?`)) {
this.props.deleteUser({
registered: row.registered,
id: row.id,
accountId: this.props.userData.account.id,
token: this.props.userData.token
});
}
}
onEdit(row) {
console.log(row);
const modal = () => (<UserForm data={row} isEdit />);
this.props.setCurrentModal(modal, 'Existing User Form');
}
onInsert() {
const modal = () => (<UserForm />);
this.props.setCurrentModal(modal, 'Add New User');
}
render() {
const options = {
searchField: (props) => (<CustomSearch {...props} />),
insertBtn: () => (<InsertButton onClick={this.onInsert} />),
toolBar: (props) => (<CustomToolBar {...props} />),
onDelete: this.onDelete,
onEdit: this.onEdit,
};
return (
<Table data={this.props.users} columns={TableColumns.USERS} options={options} title="Users" />
);
}
}
User.propTypes = {
setCurrentModal: PropTypes.func.isRequired,
fetchUsers: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired,
userData: PropTypes.object.isRequired,
users: PropTypes.array,
};
const mapStateToProps = (state) => ({
userData: state.userData.data,
users: state.tableData.users,
});
const mapDispatchToProps = (dispatch) => ({
fetchUsers: (data) => dispatch(fetchUsers(data)),
deleteUser: (data) => dispatch(deleteUser(data)),
setCurrentModal: (modal, title) => dispatch(setModal(modal, title, null, true)),
});
export default connect(mapStateToProps, mapDispatchToProps)(User);
You should test raw component because it's clear that redux works so you don't have to test it. If for some reason you want to test mapStateToProps or mapDispatchToProps export them as well and test them separately in isolation.
So if you export your raw component like this:
export { Users }; // here you export raw component without connect(...)
export default connect(mapStateToProps, mapDispatchToProps)(Users);
Then you can test it as a standard react component by importing named export, like
import { Users } from './Users';
describe('Users', () => ....
it('should render', () => ...
If you would like to test connected component because you don't want shallow rendering and maybe you render a lot of nested connected components, you need to wrap your component with <Provider> and create a store for it.
You can help yourself by using redux-mock-store that will apply middlewares for you.
Everything is very well explained in official redux documentation in Recipes > Writing tests, so my proposal is to read the whole chapter carefully. You can read there also about testing action creators, reducers and even more advanced concepts.
To read more and get better background, I encourage to check these 2 comments below from official redux / react-redux repos.
Direct link to comment: https://github.com/reactjs/react-redux/issues/325#issuecomment-199449298
Direct link to comment: https://github.com/reactjs/redux/issues/1534#issuecomment-280929259
Related question on StackOverflow:
How to Unit Test React-Redux Connected Components?