I'm trying to use react-testing-library to test react-final-form. It works with click-events, like changing a checkbox, but I can't get it working with change-events.
Below is an example using jest as a test runner:
TestForm.js:
import React from 'react';
import { Form, Field } from 'react-final-form';
const TestForm = () => (
<Form
initialValues={{ testInput: 'initial value', testCheckbox: false }}
onSubmit={() => null}
render={({ values, initialValues }) => (
<>
{console.log('VALUES', values)}
<label>
Checkbox label
<Field name="testCheckbox" component="input" type="checkbox"/>
</label>
<Field name="testInput" component="input" placeholder="placeholder" />
{values.testCheckbox !== initialValues.testCheckbox && <div>Checkbox has changed</div>}
{values.testInput !== initialValues.testInput && <div>Input has changed</div>}
</>
)}
/>
);
export default TestForm;
test.js:
import React from 'react';
import { cleanup, fireEvent, render, waitForElement } from 'react-testing-library';
import TestForm from './TestForm';
afterEach(cleanup);
describe('TestForm', () => {
it('Change checkbox', async () => {
const { getByLabelText, getByText } = render(<TestForm />);
const checkboxNode = getByLabelText('Checkbox label');
fireEvent.click(checkboxNode);
await waitForElement(() => getByText('Checkbox has changed'));
});
it('Change input', async () => {
const { getByPlaceholderText, getByText } = render(<TestForm />);
const inputNode = getByPlaceholderText('placeholder');
fireEvent.change(inputNode, { target: { value: 'new value' } });
await waitForElement(() => getByText('Input has changed'));
});
});
I run this using npx jest test.js and the first test passes but not the second.
The crucial part that doesn't seem to work is
fireEvent.change(inputNode, { target: { value: 'new value' } });
Any suggestions?
Related
I have a login Component with two inputs one is for username and the other is for password. I have onChange function for both these inputs. I have written some test cases which works fine. I need to write few more test cases which involves testing of initial state and state after updating. I have researched a lot but couldn't find suitable example for useState hooks testing with MOCHA and Enzyme.
My last test case is failing.
Mocha is mandatory for me. Any help please?
Login Component
----------------
import React from 'react'
import { Form, FormGroup, TextInput, Button } from 'carbon-components-react'
import Login16 from '#carbon/icons-react/lib/login/16'
import { PropTypes } from 'prop-types'
function LoginComponent(props) {
const { username, password, onUsernameChange, onPasswordChange, onSubmit } = props;
return (
<div className="bx--row login-box">
<div className="bx--col-xs-6 bx--col-sm-6 bx--col-md-6 bx--col-lg-6 login-form">
<Form className="form-box">
<FormGroup legendText="Login">
<div>Sign in to your account</div>
<TextInput
className="login-input"
id="username"
name="username"
value={username}
onChange={onUsernameChange}
labelText=""
placeholder="User Name"
type="text"
/>
<TextInput
className="login-input"
id="password"
name="password"
value={password}
onChange={onPasswordChange}
labelText=""
placeholder="Password"
type="password"
/>
<Button
className=""
id="login-btn"
onClick={onSubmit}
>
<Login16 className="login-icon"/> Login
</Button>
</FormGroup>
</Form>
</div>
<div className="bx--col-xs-6 bx--col-sm-6 bx--col-md-6 bx--col-lg-6 login-image">
<img src="/ibmlogo.png" alt=""/>
</div>
</div>
)
}
LoginComponent.propTypes = {
username: PropTypes.string,
password: PropTypes.string,
onUsernameChange: PropTypes.func,
onPasswordChange: PropTypes.func,
onSubmit: PropTypes.func
}
export default LoginComponent
Login.test.js
-------------
import React from 'react'
import { shallow } from 'enzyme'
import chai, { expect } from 'chai';
import chaiEnzyme from 'chai-enzyme';
import { spy } from 'sinon'
import LoginComponent from '../components/Login';
describe('Login component testing', () => {
const handleChange = spy();
const props = {
username: "",
password: "",
onUsernameChange: handleChange,
onPasswordChange: handleChange,
onSubmit: () => {}
}
const wrapper = shallow(<LoginComponent {...props}/>);
let input;
beforeEach(() => {
input = ""
})
afterEach(() => {
})
it('Should have two inputs', () => {
input = wrapper.find('.login-input');
expect(input).to.have.length(2);
})
it('Should have one button to handle onSubmit', () => {
input = wrapper.find('#login-btn');
expect(input).to.have.length(1);
})
it('Should have an initial state for username and password to be empty string or undefined', () => {
expect(wrapper.find("#username").prop('value')).to.equal('');
expect(wrapper.find("#password").prop('value')).to.equal('');
})
it('Should have props for onUsernameChange, onPasswordChange, and onSubmit', () => {
expect(wrapper.find('#username').props().onChange).to.not.be.an('undefined');
expect(wrapper.find('#password').props().onChange).to.not.be.an('undefined');
expect(wrapper.find('#login-btn').props().onClick).to.not.be.an('undefined');
})
//below test case is failing.
it('Should update state for username and password onChange', () => {
const func = wrapper.find('#username');
func.simulate('change', { target: { value: "username" } })
console.log(wrapper.find('#username').debug())
expect(wrapper.find("#username").prop('value')).to.equal('username');
})
chai.use(chaiEnzyme());
})
I am trying to write a unit test to the Login component in order to check if handleSubmit function has been called after all the input fields are filled with values.
Here is my Login.js component and my Login.test.js
//Login.js
import React, { useState } from 'react';
import axios from 'axios';
import useStyles from '../styles/LoginStyle';
import { useStatefulFields } from '../../hooks/useStatefulFields';
function Login({ success }) {
const [values, handleChange] = useStatefulFields();
const [error, setError] = useState();
const handleSubmit = async () => {
await axios.post('www.example.com', {values}, {key})
.then((res) => {
if (res.data.success) {
success();
} else {
setError(res.data.error);
}
})
};
return (
<div>
<p className={classes.p}>Personalnummer</p>
<input
type="number"
className={classes.input}
onChange={handleChange}
name="personal_number"
title="personal_number"
/>
<p className={classes.p}>Arbeitsplatz</p>
<input
type="number"
onChange={handleChange}
name="workplace"
title="workplace"
className={classes.input}
/>
<p className={classes.p}>Passwort</p>
<input
type="password"
className={classes.input}
onChange={handleChange}
name="password"
title="password"
/>
<ColorButton
id="login-button"
className={
(values.password && values.personal_number && values.workplace)
? classes.button
: classes.buttonGray
}
disabled={!values.password && !values.personal_number && !values.workplace}
size="large"
onClick={
values.password && values.personal_number && values.workplace
? handleSubmit
: () => {}
}
>
</ColorButton>
</div>
)
}
//Login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import Login from '../components/Workers/Login';
let wrapper;
beforeEach(() => {
wrapper = shallow(<Login success={() => {}} />);
});
test('should call handleSubmit', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleSubmit');
wrapper.find('input[name="workplace"]').simulate('change', { target: { name: 'workplace', value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { name: 'password', value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: { name: 'personal_number', value: 'test' } });
wrapper.find('#login-button').simulate('click');
expect(spy).toHaveBeenCalled();
});
The error that I am getting is:
TypeError: Cannot read property 'handleSubmit' of null
What am I doing wrong?
This is my functional Component.
const InputText = (props: Props) => {
return (
<div>
<StyledInput type="text" placeholder={props.placeholder} />
<br></br>
</div>
);
};
export default InputText;
and this is my test cases.
enter code here
const wrap = (props: Props) => shallow(<InputText {...props} />);
it("input text change", () => {
// const wrap = (props: Props) => mount(<StyledInput {...props} />);
const wrapper = wrap({ placeholder: "UserName" });
const usernameInput = wrapper.find("input");
usernameInput.simulate("change", { target: { value: "umesh#hcl.com" } });
expect(usernameInput.text()).toEqual("umesh#hcl.com");
});
So this test case failed with error:
Expected "Umesh#hcl.com" and recevide:"";
How can I resolve this?
Check this one
App.test.js
import React from "react";
import { shallow, configure } from "enzyme";
import Input from "./App";
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});
describe("Input Component", () => {
it("should trigger the onchange", () => {
const wrapper = shallow(<Input />);
wrapper
.find("input")
.simulate("change", { target: { value: "on typing" } });
expect(wrapper.find('input').props().value).toBe('on typing')
});
});
App.js
import React, { useState } from "react";
const InputText = props => {
const [inputText, setInput] = useState("");
const onHandleChange = e => {
setInput(e.target.value);
};
return (
<div>
<input
type="text"
value={inputText}
onChange={onHandleChange}
/>
<br />
</div>
);
};
export default InputText;
I am looking to fire a submit handler for a LoginForm. However, for some reason, instead of my mock function being called, the actual handler for the component gets fired (calling an external api). How can I ensure that my mock handler gets called instead?
The three components of interest are below (The presentational, container and the test suite)
LoginForm.js
import { Formik, Form, Field } from 'formik';
import { CustomInput } from '..';
const LoginForm = ({ initialValues, handleSubmit, validate }) => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{({ isSubmitting, handleSubmit }) => {
return (
<Form onSubmit={handleSubmit}>
<div className="d-flex flex-column justify-content-center align-items-center">
<Field
data-testid="usernameOrEmail"
type="text"
name="identifier"
placeholder="Username/Email"
component={CustomInput}
inputClass="mb-4 mt-2 text-monospace"
/>
<Field
data-testid="login-password"
type="password"
name="password"
placeholder="Password"
component={CustomInput}
inputClass="mb-4 mt-4 text-monospace"
/>
<button
data-testid="login-button"
className="btn btn-primary btn-lg mt-3 text-monospace"
type="submit"
disabled={isSubmitting}
style={{ textTransform: 'uppercase', minWidth: '12rem' }}
>
Submit
</button>
</div>
</Form>
)}}
</Formik>
);
};
export default LoginForm;
LoginPage.js
import React, { useContext } from 'react';
import { loginUser } from '../../services';
import { userContext } from '../../contexts';
import { loginValidator } from '../../helpers';
import { setAuthorizationToken, renderAlert } from '../../utils';
import LoginForm from './login-form';
const INITIAL_VALUES = { identifier: '', password: '' };
const LoginPage = props => {
const { handleUserData, handleAuthStatus } = useContext(userContext);
const handleSubmit = async (values, { setSubmitting }) => {
try {
const result = await loginUser(values);
handleAuthStatus(true);
handleUserData(result.data);
setAuthorizationToken(result.data.token);
props.history.push('/habits');
renderAlert('success', 'Login Successful');
} catch (err) {
renderAlert('error', err.message);
}
setSubmitting(false);
};
return (
<LoginForm
initialValues={INITIAL_VALUES}
validate={values => loginValidator(values)}
handleSubmit={handleSubmit}
/>
);
};
export default LoginPage;
LoginPage.spec.js
import React from 'react';
import { cleanup, getByTestId, fireEvent, wait } from 'react-testing-library';
import { renderWithRouter } from '../../../helpers';
import LoginPage from '../login-page';
afterEach(cleanup);
const handleSubmit = jest.fn();
test('<LoginPage /> renders with blank fields', () => {
const { container } = renderWithRouter(<LoginPage />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});
test('Clicking the submit button after entering values', async () => {
const { container } = renderWithRouter(<LoginPage handleSubmit={handleSubmit} />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
fireEvent.change(usernameOrEmailNode, { target: { value: fakeUser.username }});
fireEvent.change(passwordNode, { target: { value: fakeUser.password }});
fireEvent.click(submitButtonNode);
await wait(() => {
expect(handleSubmit).toHaveBeenCalledTimes(1);
});
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});```
To answer your question, you will need to first make the handleSubmit constant accessible outside LoginPage.js so that it may be mocked and then tested. For example,
LoginPage.js
export const handleSubmit = async (values, { setSubmitting }) => {
... code to handle submission
})
And in your tests - LoginPage.spec.js
jest.unmock('./login-page');
import LoginPage, otherFunctions from '../login-page'
otherFunctions.handleSubmit = jest.fn();
...
test('Clicking the submit button after entering values', () => {
...
fireEvent.click(submitButtonNode);
expect(handleSubmit).toHaveBeenCalledTimes(1);
})
I hope the above fixes your problem.
But, going by the philosophy of unit testing, the above components
must not be tested the way you are doing it. Instead your test setup
should be like this -
Add a new test file called LoginForm.spec.js that tests your LoginForm component. You would test the following in this -
Check if all input fields have been rendered.
Check if the correct handler is called on submit and with the correct parameters.
The existing test file called LoginPage.spec.js would then only test if the particular form was rendered and then you could also test
what the handleSubmit method does individually.
I believe the above would make your tests more clearer and readable
too, because of the separation of concerns and would also allow you to
test more edge cases.
Consider the following component:
import React, { Component } from "react";
import ImageUpload from "./ImageUpload/ImageUpload";
import axios from "axios";
class Dashboard extends Component {
state = {
fileName: ""
};
onFormSubmit = e => {
e.preventDefault();
const { group, country } = e.target;
axios.post("/api/dashboard", {
name: group.value,
country: country.value,
image: this.state.fileName
});
//TODO redirect the user
};
onFileNameChange = name => {
this.setState({ fileName: name });
};
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input type="text" name="group" placeholder="Group Name" />
<input type="text" name="country" placeholder="Country" />
<ImageUpload
fileName={this.state.fileName}
onFileNameChange={this.onFileNameChange}
/>
<button type="submit">Add Group</button>
</form>
</div>
);
}
}
export default Dashboard;
I am trying to fake the submission of a form and test that when onFormSubmit is being called. I make onFormSubmit a spy function. But, it is not being called at all.
import React from "react";
import Dashboard from "components/admin/Dashboard/Dashboard";
import { shallow, render } from "enzyme";
describe("The Dashboard component", () => {
it("should not regress", () => {
const wrapper = render(<Dashboard />);
expect(wrapper).toMatchSnapshot();
});
it("should submit the form and send the group to server", () => {
const wrapper = shallow(<Dashboard />);
const preventDefault = jest.fn();
const event = {
preventDefault,
target: {
group: "Samrat",
country: "Nepal"
}
};
wrapper.instance().onFormSubmit = jest.fn();
wrapper.update();
wrapper.find("form").simulate("submit", event);
expect(wrapper.instance().onFormSubmit).toHaveBeenCalledWith(event);
});
});