Mock componentDidMount lifecycle method for testing - javascript

I have a component which uses axios within componentDidMount to retrieve data from the server. When using Jest / Enzyme for unit testing the component, the tests fail with a network error.
How do I mock componentDidMount so that the axios call to the server does not happen?
The component in question uses React DnD and is a DragDropContext.
class Board extends Component {
componentDidMount() {
this.load_data();
}
load_data = () => {
// axios server calls here
}
}
export default DragDropContext(HTML5Backend)(Board);
Example test:
it('should do something', () => {
const board = shallow(<Board />);
// get the boardInstance because board is wrapped in Reactdnd DragDropContext
const boardInstance = board.dive().instance();
boardInstance.callSomeMethodToTestIt();
expect(testSomething);
}
So I just need to mock componentDidMount or load_data so that it doesn't try to call the server. If the load_data method was being passed in as a prop, I could simply set that prop to jest.fn(). However this is my top level component which does not receive any props.

With the new update to enzyme, lifecycle methods are enabled by default.
(https://airbnb.io/enzyme/docs/guides/migration-from-2-to-3.html#lifecycle-methods)
However, you can disable them in with shallow rendering as such:
const board = shallow(<Board />, { disableLifecycleMethods: true });
docs: https://airbnb.io/enzyme/docs/api/shallow.html#shallownode-options--shallowwrapper

Lifecyle methods do not defaultly work with shallow, you need to add a flag with shallow
const board = shallow(<Board />, { lifecycleExperimental: true });
Before that you can create a spy on componentDidMount to check if it was called like
const spyCDM = jest.spyOn(Board.prototype, 'componentDidMount');
and to prevent the axios request from hitting the server , you can mock the axios call using moxios

Related

How to write unit test case for componentDidMount() and export default connect(null, updateProps)(<ComponentName>) with JEST/Enzyme in React?

I have created simple react component and write test cases of components that are working correctly. I have got coverage report for the test cases.
Now, I have added react redux in my other component. this component contains componentDidMount() and export default connect(null, updateProps)(ComponentName) methods. I need to write unit test cases for these methods.
Please refer to the below code sample,
class MyComponent extends Component {
componentDidMount = () => {
//some code here
)
handleSignIn = (e) => {
//some code here
}
render() {
return (
<div>
<form onSubmit={this.handleSignIn}>
<Input
type="text"
name="inputText"
placeholder="Text"
autoFocus
required
/>
</form>
</div>
);
}
const updateProps = (dispatch) => {
return {
//some code here
};
};
export default connect(null, updateProps)(MyComponent);
In your code you have two things:
class MyComponent
and
const thisIsBasicallyAnotherComponent = connect(null, updateProps)(MyComponent);
So if you want to test your component you basically have two options. You can test your component wrapped and connected to the redux store or you can write a simple unit test for your class component as it is.
What I would recommend doing is to export your class component
- class MyComponent extends Component { // replace this
+ export class MyComponent extends Component { // with this
And then you can test your React component with Jest like any other component.
test('Link changes the class when hovered', () => {
const component = renderer.create(
<MyComponent {...mockProps} /> // !! keep in mind that you have to manually pass what you have in `updateProps` because the component is not connected to Redux store anymore
);
// ... write your test and expectations here
});
Otherwise, you can test your connected component (what is exported by default) but then you would have to wrap the component in Redux provider in order to test it.
You can find more information about testing here:
How to test components
How to test connected components
You can use Provider from react-redux or redux-mock-store to avoid need to use real reducer:
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import MyComponent from './MyComponent.jsx';
const mockStore = configureStore([thunk]);
it('does something on mount', () => {
// let's mock some Redux state
const store = mockStore({ slice1: { id: 2, name: '11' } });
mount(<Provider store={store}><MyComponent /></Provider>);
expect(store.getActions()).toContainEqual({
type: 'some-type',
payload: ....
});
});
But this is that easy only to simple actions. What if you use redux-thunk and there is some loading? There are 2 ways:
Pass redux-thunk middleware to mockStore and mock network, say, by using mock-fetch or nock. Easier to set up, but also it might be overkill if you already test your Redux directly, repeating tests for "loading failed", "loading takes long" etc also to your component would mean double work.
You can mock ../yourPath/actions.js so every action there would be plain object, not a thunk. I typically go this way.
But what about "exporting unwrapped component so we could test component in isolation, without Redux"? You see, it was working when connect was the only possible API. But now with hooks like useSelector, useDispatch, useStore in mind, it's way more reliable to make tests for "my component IN Redux" first. Otherwise with "double exports" approach we may find out than converting component from class to function means way more work on patching tests, not on component itself.

Use Redux state to initialize Axios client

I have a React/Electron application I'm working on in which I want to use data from my Redux store to initialize my Axios client. The use case is, for example, on first load of the app the user enters some information, like their username. This is pushed to the Redux store (and persisted in localStorage for future use), then used in the baseURL of the axios client for subsequent network requests.
The problem is, I can't get axios to work with react-redux and the connect() function. Axios' function exports seem to be hidden by the exported HOC, and any time I call one of its functions I get the following error:
TypeError: _Client2.default.get is not a function
My client looks something like this:
import axios from "axios";
import { connect } from "react-redux";
const Client = ({ init }) => {
return axios.create({
baseURL: `http://${init.ip}/api/${init.username}`
});
};
const mapStateToProps = state => {
return { init: state.init };
};
export default connect(
mapStateToProps,
{}
)(Client);
What am I doing wrong here?
Here in react-redux documentation https://react-redux.js.org/api/connect#connect-returns it says that The return of connect() is a wrapper function that takes your component and returns a wrapper component with the additional props it injects. So it returns react component that wraps react component. Your function returns axios client, it doesn't render anything.
I prefer to use action creators and make api calls there(Therefore I don't pass axios client or whatever). But if I decided to that I would initialize axios client inside reducer and keep in the store. And then pass it to clients as props.
const mapStateToProps = state => {
return { axios: state.axios };
};
On top of #Ozan's answer, In this case what you can do is create a main component, connect it with redux and dispatch an action on mount to initialize axios client.
You should initiate AXIOS client before you load App.js. I recommend you can use redux-axios as redux middleware and use action to call api.
https://github.com/svrcekmichal/redux-axios-middleware

ReactWrapper::setProps() error when attempting to set Redux props in component for Jest/Enzyme test

I'm writing a unit test for a React component that is connected to Redux. One of the functions is the component is that it displays data if questionReducer.showquestions == true. I have attempted to re-create this functionality in the component by setting props with wrapper.setProps({ questionReducer: { showquestions: true } }). However, when I attempt this approach, I get the error:
ReactWrapper::setProps() expects a function as its second argument
How can I properly set the props for the connected Reducer in the component I am testing?
You should test the component alone, without being connected to Redux. That allows you to give props directly to component.
Example:
export class Component_ extends React.Component {
// your component logic here
}
const mapStateToProps = {
showQuestions: questionReducer.showquestions
}
const Component = connect(mapStateToProps)(Component_)
export default Component
And then in test you can just do this
const wrapper = shallow(<Component_ showQuestions={true} />

Connect after routed component causing boolean values in props

I am currently building an app with React, React Router and React Redux
Versions:
React - v15.5.4
React Router - v4.0
React Redux - v.5.0.6
I am new to React and even newer to Redux and right when I got my head around the connect HOC I started to have this error that I cant seem to figure out.
When I connect a component to my redux store after a <switch> element and some <Route> elements. My connect within that returns my props as false boolean values where as the component within the connect has the correct props.
See code and error below for example.
Component
UserDashboardPage = connect(state => {
console.log("STATE", state);
return {
user: state.user.user,
userAuth: state.user.userAuth,
userFetched: state.user.fetched
};
})(UserDashboardPage);
UserDashboardPage.propTypes = {
user: PropTypes.shape(),
userAuth: PropTypes.shape(),
userFetched: PropTypes.boolean,
dispatch: PropTypes.func
};
CONSOLE LOG STATE
Connect with boolean prop values
Component with correct props
ERROR:
You are overwriting the local UserDashboardPage variable with the result of calling connect(). You then set PropTypes on the component returned by connect().
While you can do that, what you want in this case is to set the PropTypes of the wrapped component, not the wrapper component. Just swapping the order of execution will do it:
UserDashboardPage.propTypes = {
};
UserDashboardPage = connect(state => {
...
})(UserDashboardPage);
But you may want to consider using a different variable name for one component or the other, e.g.
UserDashboardPage.propTypes = {
};
const ConnectedUserDashboardPage = connect(state => {
...
})(UserDashboardPage);
This is usually not a problem since most people just immediately export the connected component as the default export:
export default connect(...)
The false values you're seeing are from React assigning default values to those props that failed validation. And they will always fail validation since those props are pulled from context, not passed down as normal props.
why are you passing UserDashboardPage into connect? This should be your non connected component

How can I trigger a state change in a unit test with React DOM?

I'm using the React Test Utilities to unit test some of my code. I call renderIntoDocument to render a custom component and then use findDOMNode to test out what got rendered. The trouble I'm running into is that I'm not sure how to update the state and effectively trigger a re-render within the context of a unit test.
Here's some sample code -- pay attention to the code comment:
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
import MyCustomComponent from '../../app/components/MyCustomComponent';
describe('My Test Suite', () => {
let component, node;
test('verify state change', () => {
const items = [{'value': '1'}];
component = TestUtils.renderIntoDocument(
<MyCustomComponent items={items} />
);
node = ReactDOM.findDOMNode(component);
expect(node.querySelector('input[type=text]').value).toEqual('1');
component.state.items = [{'value': '2'}];
// do something here to trigger a re-render?
expect(node.querySelector('input[type=text]').value).toEqual('2');
});
});
Unfortunately it seems simply changing the state variable doesn't do anything. And I can't call component.componentWillReceiveProps() because that doesn't seem to be defined.
Please note that I do want the same component to call its render function rather than replacing it with, effectively, a brand new component. The reason is because I found a bug where the component was rendering things based on this.props instead of this.state, and I want a test to show that it's always using data from the state and not from the initial values.
Enzyme from AirBnb has some great utilities for this. You'll need to install the dependencies but it's simple enough to get it configured. Then, you can simply call Enzyme's setState method on your component instance. An important note – your "component instance" in this case is a shallow rendered component. Your code would look something like this:
import React from 'react';
import MyCustomComponent from '../../app/components/MyCustomComponent';
import { shallow, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// configure your adapter
configure({ adapter: new Adapter() });
describe('My Test Suite', () => {
test('verify state change', () => {
const items = [{'value': '1'}];
const wrapper = shallow(<MyCustomComponent items={items} />);
// find your shallow rendered component and check its value
expect(wrapper.find('input[type="text"]').value).toEqual('1');
// set the state of the component
wrapper.setState({ items: [{'value': '2'}] });
// state should be updated, make sure its value was properly set
expect(wrapper.find('input[type="text"]').value).toEqual('2');
});
});
All of this assumes that you are using state in your component properly. In your case, items appears to be passed in as a prop. If you are setting state by just copying props, you may want to rethink your strategy. In any case, this approach should be identical to how state updates in React work – you're operating on the same component instance without unmounting and remounting the component. Hope this helps.

Categories