Unable to stimulate change event in child component React redux jest - reactjs

I am new to react redux and I am trying to unit test FormContainer component. But I am unable to simulate change event on Form component using enzyme or jest. In wrapper.instance() I am receiving null but I dont' think that should. If not then what is correct way of testing FormContainer.js file, because when I ran test coverage it showed me that change function was uncovered.
FormContainer.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Form from "../components/Form";
import { isNumeric } from "../utils";
class FormContainer extends Component {
constructor(props) {
super(props);
this.state = {
number: "",
error: false
};
}
// componentWillReceiveProps(nextProps) {
// console.log(nextProps, this.props, "main component");
// }
change = event => {
let value = event.target.value;
if (isNumeric(value) && value < 1001 && value >= 0) {
if (value === "0") {
this.setState({
...this.state,
error: true
});
return;
} else {
this.setState({
...this.state,
[event.target.name]: value,
error: false
});
}
} else {
this.setState({ ...this.state, error: true });
}
};
render() {
return (
<Form change={this.change} value={this.state} error={this.state.error} />
);
}
}
const mapStateToProps = state => {
return { ...state };
};
export default connect(mapStateToProps)(FormContainer);
FormContainer.test.js
import React from 'react';
import configureStore from "redux-mock-store";
import { shallow,mount } from 'enzyme';
import FormContainer from '../../containers/FormContainer';
import { findByAttr } from '../../utils';
import Form from '../../components/Form';
describe("Testing Form container", () => {
let wrapper;
let store;
beforeEach(() => {
const initialState = {}
const mockStore = configureStore();
store = mockStore(initialState);
wrapper = shallow(<FormContainer store={store} />);
});
it("Should render without error ", () => {
expect(wrapper.exists()).toBe(true);
});
it("Simulate change", () => {
let component = wrapper.find(Form);
const mockChange = jest.fn();
wrapper.instance().change = mockChange;
});
});
Testing Form container › Simulate change
TypeError: Cannot set property 'change' of null
22 | let component = wrapper.find(Form);
23 | const mockChange = jest.fn();
> 24 | wrapper.instance().change = mockChange;
| ^
25 | });
26 | });
at Object.<anonymous> (src/__test__/containers/FormContainer.test.js:24:9)

Dont need to import import Form from '../../components/Form'; since we are only testing FormContainer component, so remove it.
and there is no event listener called change, Use onChange instead.
<Form onChange={this.change} value={this.state} error={this.state.error} />
Try changing this spec:
it("Simulate change", () => {
let component = wrapper.find(Form);
const mockChange = jest.fn();
wrapper.instance().change = mockChange;
});
To:
it("Simulate change", () => {
(wrapper.find(Form).first()).simulate('change');
expect(wrapper.instance().change.mock.calls.length).toBe(1);
});

Related

App not re-rendering on history.push when run with jest

I'm trying to test my LoginForm component using jest and react-testing-library. When the login form is submitted successfully, my handleLoginSuccess function is supposed to set the 'user' item on localStorage and navigate the user back to the home page using history.push(). This works in my browser in the dev environment, but when I render the component using Jest and mock out the API, localStorage gets updated but the navigation to '/' doesn't happen.
I've tried setting localStorage before calling history.push(). I'm not sure what is responsible for re-rendering in this case, and why it works in dev but not test.
Login.test.jsx
import 'babel-polyfill'
import React from 'react'
import {withRouter} from 'react-router'
import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '#testing-library/react'
import Login from '../../pages/Login'
import API from '../../util/api'
jest.mock('../../util/api')
function renderWithRouter(
ui,
{route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},
) {
return {
...render(<Router history={history}>{ui}</Router>),
// adding `history` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
history,
}
}
describe('When a user submits the login button', () => {
test('it allows the user to login', async () => {
const fakeUserResponse = {'status': 200, 'data': { 'user': 'Leo' } }
API.mockImplementation(() => {
return {
post: () => {
return Promise.resolve(fakeUserResponse)
}
}
})
const route = '/arbitrary-route'
const {getByLabelText, getByText, findByText} = renderWithRouter(<Login />, {route})
fireEvent.change(getByLabelText(/email/i), {target: {value: 'email#gmail.com '}})
fireEvent.change(getByLabelText(/password/i), {target: {value: 'Foobar123'}})
fireEvent.click(getByText(/Log in/i))
const logout = await findByText(/Log Out/i)
expect(JSON.parse(window.localStorage.getItem('vector-user'))).toEqual(fakeUserResponse.data.user)
})
})
relevant parts of LoginForm.jsx
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
disableActions: false,
formErrors: null,
};
}
handleLoginSuccess = () => {
const { loginSuccessCallback, redirectOnLogin, history } = { ...this.props };
if (loginSuccessCallback) {
loginSuccessCallback();
} else {
history.push('/');
}
}
loginUser = ({ user }) => {
localStorage.setItem('vector-user', JSON.stringify(user));
}
handleLoginResponse = (response) => {
if (response.status !== 200) {
this.handleResponseErrors(response.errors);
} else {
this.loginUser(response.data);
this.handleLoginSuccess();
}
}
handleLoginSubmit = (event) => {
event.preventDefault();
const {
disableActions, email, password
} = { ...this.state };
if (disableActions === true) {
return false;
}
const validator = new Validator();
if (!validator.validateForm(event.target)) {
this.handleResponseErrors(validator.errors);
return false;
}
this.setState(prevState => ({ ...prevState, disableActions: true }));
new API().post('login', { email, password }).then(this.handleLoginResponse);
return true;
}
}
Login.jsx
import React from 'react';
import { withRouter, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import LoginForm from '../components/LoginForm';
class Login extends React.Component {
constructor({ location }) {
super();
const originalRequest = location.state && location.state.originalRequest;
this.state = {
originalRequest
};
}
render() {
const { originalRequest } = { ...this.state };
return (
<div>
<h1>Login</h1>
<LoginForm redirectOnLogin={originalRequest && originalRequest.pathname} />
<Link to="/forgot">Forgot your password?</Link>
</div>
);
}
}
Login.propTypes = {
location: PropTypes.shape({
state: PropTypes.shape({
originalRequest: PropTypes.shape({
pathname: PropTypes.string
})
})
})
};
export default withRouter(Login);
Currently the await findByText() times out.
I think that's because in your tests you're not rendering any Route components. Without those react-router has no way to know what to render when the route changes. It will always render Login.

how to test a react component after data is fetch in componentDidMount?

I have a react component that renders conditionally (renders if data is fetched otherwise returns null) and I want to test this with jest & enzyme. The problem that I'm having is I want to test one of the methods in the class but .instance() keeps returning null so it doesn't allow me to test the instance.
my code looks something like this
export default class MyComponent extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchData.then(() =>
this.setState({ loaded: true });
);
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.loaded) {
// render stuff here
} else {
return null;
}
}
}
and in the test I want to test
describe('myComponent', () => {
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>);
const method = shallowWrapper.instance().methodThatIWantToTest();
....such and such
});
});
but it looks like MyComponent only returns null so shallowWrapper.instance() returns null as well. I tried shallowWrapper.update() and many other things but it seems it doesn't want to render at all.. How do I wait for my component to be updated and then starts expect statement?
has anyone had a similar issue as mine and know how to work around this?
It is render result and not an instance that is null. shallowWrapper.instance() is an instance of component class, it cannot be null for stateful component. As the reference states:
Returns (React 16.x)
ReactComponent: The stateful React component instance.
null: If stateless React component was wrapped.
While shallowWrapper.html() will be initially null indeed.
There is a mistake in original code, it should be this.state.loaded and not this.loaded:
MyComponent extends React.Component {
state = { loaded: false };
componentDidMount() {
this.props.fetchData.then(() => {
this.setState({ loaded: true });
});
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.state.loaded) {
return <p>hi</p>;
} else {
return null;
}
}
}
componentDidMount and methodThatIWantToTest should be preferably considered different units. They belong to different tests. In case methodThatIWantToTest is called in lifecycle hooks, it may be stubbed in componentDidMount test:
it('should fetch data', async () => {
const props = { fetchData: Promise.resolve('data') };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
await props.fetchData;
expect(shallowWrapper.html()).toBe('<p>hi</p>');
});
Then the method can be tested separately. Lifecycle hooks can be disabled to reduce number of moving parts:
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>, {disableLifecycleMethods: true});
const result = shallowWrapper.instance().methodThatIWantToTest();
expect(result).toBe(...);
});
Here is a working example:
myComponent.js
import * as React from 'react';
export default class MyComponent extends React.Component {
constructor(...props) {
super(...props);
this.state = { loaded: false };
}
componentDidMount() {
this.props.fetchData().then(() =>
this.setState({ loaded: true })
);
}
methodThatIWantToTest() {
return 'result';
}
render() {
if (this.state.loaded) {
return <div>loaded</div>;
} else {
return null;
}
}
}
myComponent.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './myComponent';
describe('myComponent', () => {
it('should do some stuff', async () => {
const fetchData = jest.fn(() => Promise.resolve());
const props = { fetchData };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
expect(shallowWrapper.instance().methodThatIWantToTest()).toBe('result');
// pause the test and let the event loop cycle so the callback
// queued by then() within componentDidMount can run
await Promise.resolve();
expect(shallowWrapper.html()).toBe('<div>loaded</div>');
});
});

jest.fn() value must be a mock function or spy received function: [Function getTemplates]

I have a function in module called handlelistOfTemplates which calls actions defined in another file. I want to test when handlelistOfTemplates is called the function in actions file is called with correct parameters.
My container component :
import React from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import * as getData from '../../services/DataService';
class Container extends React.Component {
constructor(props){
super(props)
this.props.actions.getTemplates(1);
this.state = {
value: 1
}
}
handlelistOfTemplates = (template, index, value) => {
this.props.selectedTemplate(template);
this.setState({ value });
this.props.actions.getTemplates(template);
};
componentDidMount() {
}
render() {
return(
<ListOfTemplates listOfTemplates={this.props.listOfTemplates} value={this.state.value} onChange={this.handlelistOfTemplates}/>
);
}
}
function mapStateToProps({state}) {
return {
listOfTemplates: state.listOfTemplates
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(getData, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(Container);
And my test :
import React from 'react';
import sinon from 'sinon';
import expect from 'expect';
import { shallow } from 'enzyme';
import PropTypes from 'prop-types';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { createMockStore, createMockDispatch } from 'redux-test-utils';
import Container from './Container';
const shallowWithStore = (component, store) => {
const context = {
store,
muiTheme: getMuiTheme(),
};
const childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
return shallow(component, { context, childContextTypes });
};
let store;
const loadComponent = (testState) => {
const props = {
actions: {
getTemplates: () => {return Promise.resolve()}
}
}
store = createMockStore(testState)
return shallowWithStore(<Container {...props}/>, store);
}
const getFakeState = () => {
return {
listOfTemplates: [],
};
}
describe('Container', () => {
let testState, component;
describe("when Appeal template is selected from select template dropdown", () => {
beforeAll(() => {
testState = getFakeState();
component = loadComponent(testState);
});
fit('should update the content in editor', (done) => {
component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
component.update();
done();
expect(component.dive().state().value).toEqual(2) // error value still is at 1
expect(component.instance().props.actions.getTemplates).toHaveBeenCalled();
});
});
});
When I run the above test I get the following error.
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function getTemplates]
Is there something else I should be running to get this to work ?may be my mock is not correct.
even i tried doing this :
jest.spyon(component.instance().props.actions, 'getTemplates'); before expect the error remains same.
Also, when i checking the component's local state has been modified or not. I'm not getting the updated state.
when i call component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
the component.dive().state().value should become 2 but it is 1 instead.
Could you please help me where i'm doing wrong?
You should pass a Mock function getTemplate to your component, otherwise jest won't be able to check whether it was called or not.
You can do that like this: (notice jest.fn() )
const props = {
actions: {
getTemplates: jest.fn(() => Promise.resolve())
}
}

Enzyme test DOM document using jsdom

I’m trying to test a simple component in React using jsdom dependency. On my component I want to use plain javascript and I’m using document.getElementById() and enzyme is complaining about that. So I decided to try jsdom. However I don’t know if I’m passing the proper html string to it and if I need to use enzyme’s mount instead of shallow.
This is my react component code:
import React, { Component } from 'react';
class LikesAmount extends Component {
constructor() {
super();
this.state = {
totalLikes: null,
counter: 0,
};
}
componentDidMount() {
if (this.state.counter === 0) {
this.setState({
totalLikes: document.getElementById('likesAmount').dataset.likes,
counter: this.state.counter += 1,
});
}
}
render() {
return (
<div id="likesAmount" data-likes="1000">
<h3>Total likes: {this.state.totalLikes}</h3>
</div>
);
}
}
export default LikesAmount;
And this is my react component TEST code:
import React from 'react';
import { shallow } from 'enzyme';
import jsdom from 'jsdom';
import LikesAmount from './likesAmount';
const { JSDOM } = jsdom;
const { document } = (new JSDOM('<!doctype html><html><body><div id="likesAmount" data-likes="1000"></div></body></html>')).window;
global.document = document;
const wait = () => new Promise(resolve => setImmediate(resolve));
describe('likesAmount component', () => {
let wrapper;
beforeEach(async () => {
wrapper = shallow(<LikesAmount />);
await wait();
wrapper.update();
});
it('Return 1000 likes', async () => {
const likes = 1000;
expect(wrapper.find('h3').text()).toBe(`Total likes: ${likes}`);
});
});
The error that I'm getting is "Cannot read property 'find' of undefined". So I suppose it has something to do with jsdom because with a normal shallow passing the component as a parameter reads me the wrapper variable assign to it and the .find method...

Reactjs unit test

I have a LoginInfo component and under this component i am calling one more child component. I am trying to write unit test case for the components using jest,enzyme and react test utils. partially i have wrote the test cases but not sure how i can write test for child component (LoginInfoEdit). that line i am not able to cover.
import React from 'react';
import { LoginInfoEdit } from './LoginInfoEdit'
class LoginInfo extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoginInfo: false
}
}
openEdit() {
this.setState({ isLoginInfo: true })
}
closeEdit() {
this.setState({ isLoginInfo: false })
}
OpenEditForUpdate(e) {
e.preventDefault();
this.openEdit();
}
render() {
return (
<div>
<form>
<div>
some text
</div>
<LoginInfoEdit loginid={this.props.loginid} openloginedit={this.state.isLoginInfo} onClose={this.closeEdit.bind(this)}>
</LoginInfoEdit>
</form>
</div>
)
}
}
export default LoginInfo
Unit test is Below--------
import React from 'react'
import { shallow } from 'enzyme';
import LoginInfo from './LoginInfo'
import LoginInfoEdit from './LoginInfoEdit'
const props = {
loginid: "1",
openloginedit: false,
};
describe('LoginInfo component', () => {
let LoginInfo = null;
let editButton = null;
beforeEach(() => {
LoginInfo = shallow(<LoginInfo {...props}/>);
editButton = LoginInfo.find('button[name="edit"]')
})
it('checks everything set properly', () => {
editButton.simulate('click', { preventDefault: () => { } });
expect(LoginInfo.state('isloginedit')).toEqual(true)
})
it('renders child', () => {
expect(LoginInfo.find('LoginInfoEdit').length).toEqual(1)
});
it('passes proper props to the child', () => {
const expected = {
loginid: "1",
openloginedit: false,
onClose: LoginInfo.instance().closeEdit.bind(this),
};
expect(LoginInfo.find('LoginInfoEdit').props()).toEqual(expected)
})
})
Usually in such cases I care only about checking whether we render the child and pass props we want to the child like:
let component;
const props = someProps;
beforeEach(() => { component = mount(<LoginInfo { ..props } />); });
it('renders child', () => {
expect(component.find('LoginInfoEdit').length).to.eql(1)
});
it('passes proper props to the child', () => {
const expected = {
loginid: someVal,
openloginedit: someotherVal,
onClose: component.instance().closeEdit,
};
expect(component.find('LoginInfoEdit').props()).to.eql(expected)
});
and then I just test the children (in this case, LoginInfoEdit) separately from the parent

Resources