Can anyone provide an example on React-Redux Jest testing? - reactjs

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?

Related

Is it possible to use redux and axios in react component library?

I am creating a page in React. Lets say for eg. "Conatct us" page. This whole component must be reusable. So that other teams can use it as it is. This component will have its own redux store and api calls using axios.
What I want to confirm that if I export this "Contact Us" module as npm package, will it work fine for other teams? Why I am asking this is because other teams project will have their own redux store and axios instance. And I think we can have only one redux store in an app and maybe one axios interceptors (I may be wrong about axios though)
Could anyone help me out, what can be done in this case? One thing is sure that I will have to export this whole component as npm package.
I'm going to answer here to give you more details:
Let's say your component looks like this:
AboutUs:
import React, { Component } from "react";
import PropTypes from "prop-types";
export class AboutUs extends Component {
componentDidMount() {
const { fetchData } = this.props;
fetchData();
}
render() {
const { data, loading, error } = this.props;
if (loading) return <p>Loading</p>;
if (error) return <p>{error}</p>;
return (
// whatever you want to do with the data prop that comes from the fetch.
)
}
}
AboutUs.defaultProps = {
error: null,
};
// Here you declare what is needed for your component to work.
AboutUs.propTypes = {
error: PropTypes.string,
data: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
}),
fetchData: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
This component just takes a few props in order to work and the fetchData function will be a dispatch of any redux action.
So in one of the apps that are going to use the component library, assuming that they have their own store, you could do something like this.
In the component where you're planning to use the AboutUs component.
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
// this is the action that performs the data fetching flow.
import { fetchAboutUs } from "redux-modules/aboutUs/actions";
// The component that is above
import { AboutUs } from "your-component-library";
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
fetchData: fetchDashboard,
},
dispatch
);
};
const mapStateToProps = state => ({
loading: state.aboutUsReducer.loading,
error: state.aboutUsReducer.error,
data: state.aboutUsReducer.data,
});
const ReduxAboutUs = connect(
mapStateToProps,
mapDispatchToProps
)(AboutUs);
// Use your connected redux component in the app.
const SampleComponent = () => {
return <ReduxAboutUs />
}
This ensures that your component can work out of the box without redux, because you can explicitly use it without the redux dependency and just pass regular props and it will continue working. Also if you have different applications where you are going to use it you will have the control of which part of the store you want to use to inject the props for this component. Proptypes are quite useful here, because we're enforcing a few props in order let the devs what do we need to pass in order for the component to work properly.

Proper seperation of React component in container and presentational components for redux

I'm trying to seperate a component like mentioned in the title.
According to the redux tutorial for react it's a best practice to split components up.
Until now I have the following components:
ReduxTestNetwork
import React, {Component} from 'react';
import {Edge, Network, Node} from '#lifeomic/react-vis-network';
import { connect } from "react-redux";
import MyNetwork from "./MyNetwork";
...
const mapStateToProps = state => {
return { nodes: state.nodes,edges: state.edges };
};
const VisNetwork = ({nodes,edges}) => (
<MyNetwork nodes={nodes} edges={edges} options={options}/>
);
const ReduxTestNetwork = connect(mapStateToProps)(VisNetwork);
export default ReduxTestNetwork;
MyNetwork
import React, {Component} from 'react';
import {Edge, Network, Node} from '#lifeomic/react-vis-network';
import connect from "react-redux/es/connect/connect";
import {addNode} from "../actions";
const mapDispatchToProps = dispatch => {
return {
addNode: node => dispatch(addNode(node))
};
};
class MyNetwork extends Component {
constructor(props) {
super(props);
this.state = {nodes: props.nodes, edges: props.edges, options:props.options};
}
componentDidMount() {
console.log('I just mounted')
//this.onClick();
}
onClick(e){
console.log(e)
console.log(this)
/* this.props.addNode({id:5,label:'Node 5'});
this.setState(prevState => ({
nodes: [...prevState.nodes, {id:5,label:'Node 5'}]
}));*/
}
render() {
const nodes = this.state.nodes.map(node => (
<Node key={node.id} {...node}/>
));
const edges = this.state.edges.map(edge => (
<Edge key={edge.id} {...edge}/>
));
return (
<div id='network'>
<Network options={this.state.options} ref={(reduxTestNetwork) => {
window.reduxTestNetwork = reduxTestNetwork
}} onSelectNode={this.onClick.bind(this)}>
{nodes}
{edges}
</Network>
</div>);
}
}
const SVNetwork = connect(null, mapDispatchToProps)(MyNetwork);
export default SVNetwork;
I connected ReduxTestNetwork to the store to obtain the state as props and MyNetwork to be able to dispatch.
I read that presentational components should only be used to display elements and the container components should include the logic how and what to display. But I need in MyNetwork some logic also to interact with the Network component which uses a 3rd party library.
So my questions are:
Is my seperation correct?
Where should I put logic for (for example) calculating the size or color of displayed nodes?
Thanks in advance
Several things:
You don't need to use connect twice. Pass mapStateToProps and mapDispatchToProps at the same time, both on the container.
If you want to follow the path of purely presentational components, consider using a side effect library: refract, sagas, thunk... they have patterns to deal with your logic outside of the component.
If you prefer a more hand made approach, you could move every method you need to the container and pass to the component via props only the data and the function references to modify it.

Either not changing state or not rerendering redux

I am dispatching an action to a reducer to change that state, however nothing is re-rending. Below is some code however since the project is small I'm including a link to the repo on Github here
import React, {Component} from 'react';
import {connect} from 'react-redux';
import * as action from '../action';
import {addTodo} from "../action";
//this is used to get the text from the input to create a new task
let text = "";
class AddTodo extends Component
{
render()
{
return(
<div>
<input type="text" id="taskText" onChange={ () => { text = document.querySelector("#taskText").value;} }/>
<button onClick={this.props.addTodo}>+</button>
</div>
);
}
}
const mapDispatchToProps = (dispatch) =>
{
return (
{
addTodo: () =>
{
//console.log(`making action with text: ${text}`);
addTodo.payload = {text:text, completed:false};
dispatch(addTodo);
}
}
);
};
export default connect(null, mapDispatchToProps)(AddTodo);
Since you haven't shared all the related code here most people don't check your repo. In the future, try to share the related (just related code) here. If you do so, you will get faster and better answers.
I will share how you solve your problems first.
Uncomment TodoList component in the App.js file.
In TodoList component your are using the wrong state. Your todos in the todo reducer.
So:
taskList: state.list.tasks.todo.concat( state.list.tasks.completed ),
You are mutating your state in todo reducer. Don't mutate your state.
Change the related part:
case "ADD_TODO":
return { ...state, tasks: { ...state.tasks, todo: [ ...state.tasks.todo, action.payload ] } };
Other than those problems, you are using some bad practices. For example, why do you keep the text in a variable like that in your AddTodo component? Use the local state here, this is the proper React way.
Also, your action creators are not so properly defined. They are functions, returning an object. Now, your AddTodo component would be like this:
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../action";
class AddTodo extends Component {
state = {
text: "",
}
handleChange = e => this.setState( { text: e.target.value } );
handleSubmit = () => {
const newTodo = { text: this.state.text, completed: false };
this.props.addTodo( newTodo );
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<button onClick={this.handleSubmit}>+</button>
</div>
);
}
}
const mapDispatchToProps =
{
addTodo: actions.addTodo,
};
export default connect( null, mapDispatchToProps )( AddTodo );
Or even, you don't need here a separate mapDispatchToProps if you like. You can use the connect part like this:
export default connect( null, { addTodo: actions.addTodo } )( AddTodo );
Then, your related action creator would be like this:
export const addTodo = newTodo => ({
type: "ADD_TODO",
payload: newTodo
});
So, I suggest reading more good tutorials about Redux :) Just give yourself a little bit more time. Follow some good tutorials until you are sure that you know the best practices and proper ways. For example, if you study Redux, the first rule is not mutating your state. You are doing it everywhere :) Also, try to keep your state simple, not so nested.
Good luck.

use react-redux with connect() and {...this.props}

I cannot figure out, how to make right solution, when I want to call action in my container from other component, by the way I want to use spread operator because I need to pass too many parametrs in my component and don't want describe all of them.
I know I can pass all props from redux store via props, like this example in Menu, but my component too nested, and I have to send props in eighter component in nest
render() {
return (
<div className="wrapper">
<Menu {...this.props} />
</div>
);
}
}
const mapStateToProps = reduxStore => (
{
app: reduxStore.app
}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapStateToProps, mapDispatchToProps)(App);
So, I decided to connect my nested component with redux store, because I need to work from my nested component with store and actions in main container component. But this solution doesn't work, because i use spread operator to my nested component.
render() {
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...this.props} />;
}
And using spread operator is really important because component get too much different parameters from its parent component, and if i don't use {...this.props}, I have to write like this:
render() {
const { to, onlyActiveOnIndex, className, specialIcons } = this.props;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} to={to} specialIcons={specialIcons} onlyActiveOnIndex={onlyActiveOnIndex} className={className} >{this.props.children}</Link>;
}
But also, I have to connect to common redux store, and when I connected, occurs an Error, because of my component use {...this.props} and it get all props, including actions from container and component doesn't know what do with them. I find one solution of this proplem, but I'm not sure that it is right variant. I clone props with spread operators, but delete property that contain new functions (actions) from common store.
render() {
let oldProps = {...this.props};
delete oldProps.appActions;
delete oldProps.app;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...oldProps} >{this.props.children}</Link>;
}
}
const mapState = reduxStore => ({app: reduxStore.app}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapState, mapDispatchToProps)(NavLink);
I'm guessing that I don't understand something basic and global in react-redux or I use bad practice. May be I should use higher order components in React? but now I don't know how to make it better.
Here is a functional example. I made it for a personal project. I removed the useless code for the purpose of the example.
Something you might want to get is eslint, it will show you basic mistake people are making while coding.
For example, it will say that you having declared your PropTypes. In your code, where does it say what app is? Sure it's coming from reduxStore.app but what kind of PropTypes is it?
Also, you shouldn't link all the reduxStore state to your component. You should just import what you really need. In my example, I import only users from state.app.users. If I had more, or want all elements of the reducer state, I would import all of them individually and then declare the props like this:
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
Because JavaScript isn't a typed language, the PropTypes like above help you make typed validation. You can also see the props actions which contains all the functions you import in AppActions in your case.
To see how to use the function from the action afterward, look at my componentWillMount()
import React, { Component, PropTypes } from 'react';
import { ListView} from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as app from '../../actions/appActions';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: ds.cloneWithRows(props.users.toJS()),
};
}
componentWillMount() {
this.props.actions.load();
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
dataSource: ds.cloneWithRows(nextProps.users),
});
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
enableEmptySections
renderRow={
(rowData) => <User haveLunch={rowData.haveLunch} name={rowData.name} />
}
/>
);
}
}
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
function mapStateToProps(state) {
return {
users: state.app.users,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(app, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Hope this will help ya ;)

Testing React Component with Enzyme and Mocha

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);

Resources