Testing reduxForm component methods with jest and enzyme - reactjs

I'm testing a reduxForm component with jest and enzyme and I don't know how i can test methods within this component.
I'm exporting the component and testing it itself, I have access to the methods but the component don't recognize the reduxForm field props like meta, touched, error, input e.t.c.
How I can mock these or test my component with reduxForm, not as a separate one
The tested component is Login component and must render 2 fields.
This is the Login component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block btn-clicked">Login </button>
</div>
</form>
</div>
</div>
);
}
}
export const validate = (values) => {
const errors = {}
const dummyData = {
username: 'admin',
password: 'admin'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
This is the testing file:
import React from 'react'
import { shallow, mount } from 'enzyme'
import { Login } from '../../components/login/login'
import { validate } from '../../components/login/login'
const mockHandleSubmit = jest.fn()
const renderField = jest.fn()
const mockRenderPasswordField = jest.fn()
const wrapper = shallow(<Login handleSubmit={mockHandleSubmit} />)
const loginButton = wrapper.find('#login_button')
describe('<Login /> render', () => {
it('render the <Login /> component without crashing', () => {
expect(wrapper.length).toEqual(1)
})
it(' call handlesSubmit when submit button is clicked', () => {
loginButton.simulate('click')
expect(mockHandleSubmit).toHaveBeenCalled()
})
})
describe('renderField and renderPasswordField functions', () => {
it('renderField', () => {
expect(wrapper.instance().renderField()).toEqual(1)
})
})
And the error it gives me

To mount a reduxForm wrapped component, you need to wrap your component in
mount(
<Provider store={store}>
<Login {...props}/>
</Provider>
)
note that you need to create a custom store beforehand like this
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
store = createStore(combineReducers({ form: formReducer }))
You may refer to this repo for how to test redux form properly with integration and unit tests.
https://github.com/tylercollier/redux-form-test

Related

How to get data React UseForm

I'm new to React and still am trying to make sense of a few things. I'm using the react-form-dom library and after looking into the documentation I can't figure out what's going on with my project.
I have created a custom input component that takes in a few parameters. The problem is that when I try to log the value of the inputs I get undefined.
Input.jsx
import React, { useState } from 'react';
import './Input.scss';
export default function Input(props, {register, errors}) {
const [inputVal, setInputVal] = useState('');
const nameChange = (e) => {
setInputVal(e.target.value);
props.nameChange(e.target.value);
}
const legend = <span className='legend'>{props?.legend}</span>;
const showLength = <span className='legend'>{props.maxLength - inputVal.length}</span>;
return (
<div className="input-holder">
<input
ref={register}
type={props.type || 'text'}
id={props.id}
placeholder={props.placeholder}
value={inputVal}
onInput={nameChange}
maxLength={props.maxLength || ""}
/>
{ props.legend ? legend : null}
{ props.showLength && props.maxLength ? showLength : null }
</div>
)
}
Form.jsx
import React from 'react';
import { useForm } from "react-hook-form";
import Button from './Button';
import Input from './Input';
export default function UserForm(props) {
const { register, handleSubmit } = useForm();
function submitForm(data) {
console.log(data)
}
return (
<div className="user-form">
<form onSubmit={handleSubmit(submitForm)}>
<Input
placeholder="Name"
nameChange={(name) => props.userName(name)}
showLength="true"
maxLength="35"
legend="Remaining Char: "
id="name"
register={register}
/>
<Input
placeholder="E-mail"
type="email"
id="email"
register={register}
/>
<Button label="Buy" />
</form>
</div>
)
}
It seems that you are using react-hook-form
Checkout the example here
The onChange you are passing to the input tags are no longer needed with react-hook-form beacuse of register functionality of react-hook-form
also validations will be done react-hook-form
import React from 'react';
import { useForm } from "react-hook-form";
export default function UserForm(props) {
const { register, handleSubmit } = useForm();
const submitForm = (data) => {
console.log(data);
}
const onError = (err) => {
console.error(err);
}
return (
<div className="user-form">
<form onSubmit={handleSubmit(submitForm, onError)}>
<input
{...register("name", { //handles onChange, value, name, ref
maxLength: 35 // Your validations
})}
/>
<button>Buy</button>
</form>
</div>
)
}
Checkout docs for register
Form File Example

React redux-form - error while testing - "TypeError: handleSubmit is not a function"

Im struggling to find a solution when testing a redux-form component. The problem is that only when I test simply if the component is rendering it gives me an error: "TypeError: handleSubmit is not a function", but the app is working fine, as expected.
I've tried to solve it just to make handleSubmit a function and not taking it from props, but then the app doesn't work. When the submit form is correct it must navigate to /landing page, but instead just re-render the login component.
The component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
// DOESN'T WORK!!!
// handleSubmit(formValues){
// //console.log(formValues);
// }
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={ /*this.*/handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block">Login </button>
</div>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {}
const dummyData = {
username: 'admin',
password: '123'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
The testing file:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login />)
expect(wrapper.length).toEqual(1)
})
// it('navigate to /landing page when the form is submit correctly', () => {
// })
})
You are consuming a prop function from your HOC reduxForm on your render method. But on the test file, you are importing the component without the HOC on top of it, which means that prop/function is not available. You have to provide Login with a mock handleSubmit prop function.
Try:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login handleSubmit={() => {}} />)
expect(wrapper.length).toEqual(1)
})
})
you need pass onSubmit on initialization:
export default reduxForm({
validate,
form: 'LoginForm',
onSubmit: this.onSubmit // here
})(Login)
or in props:
<Component>
<Login onSubmit={this.onSubmit}/>
</Component>
First export the redux-form decorated class
export const DecoratedLogin = reduxForm({
validate,
form: 'LoginForm'
})(Login);
Then use it instead of the plain class.
import { DecoratedLogin } from './login'
and you should be able to access the props from redux-form.

How to use reduxForm() and connect() together for class component in react?

added reducer in reducer file
const reducers = {
// ... other reducers here ...
form: formReducer
};
I am using redux-form v 7.1.2 with react v 16 and using reduxForm()`` but there is alreadyconnect()` applied in this class, tried 2 approached to add both HOC in the same page but failed on both.
below is the relevant code of my files , please check and let me know what is missing ?
Using named export
renderField.jsx
import React from 'react';
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
export default renderField;
Register.jsx
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { renderField } from './renderField';
class RegisterPage extends React.Component {
constructor (props) {
super(props);
this.state = {
user: {
firstName: ''
},
submitted: false
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit (values) {
this.setState({ submitted: true });
const { user } = this.state;
const { dispatch } = this.props;
dispatch(userActions.register(user));
}
render () {
const { registering } = this.props;
return (
<div className="col-md-6 col-md-offset-3">
<h2>Register</h2>
<form onSubmit={this.handleSubmit} >
<div className="form-group">
<Field
name="firstName"
type="text"
label="First Name"
className="form-control"
component={renderField}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary" disabled={this.pristine} >Register</button>
{ registering && <Loading /> }
<Link to="/login" className="btn btn-link">Cancel</Link>
</div>
</form>
</div>
);
}
}
below class definition , here is connect and reduxForm usage
RegisterPage = reduxForm({
form: 'register',
destroyOnUnmount: false
})(RegisterPage);
const connectedRegisterPage = connect(mapStateToProps)(RegisterPage);
export { connectedRegisterPage as RegisterPage };
App.jsx
import { RegisterPage } from '../RegisterPage';
<Route path="/register" component={RegisterPage} />
BUT this gives multiple warning in console
Failed prop type: The prop component is marked as required in
Field, but its value is undefined. in Field (created by
RegisterPage)
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: undefined. You likely forgot to export your
component from the file it's defined in. Check the render method of
ConnectedField.
with default export
Register.jsx
RegisterPage = reduxForm({
form: 'register'
})(RegisterPage);
const connectedRegisterPage = connect(mapStateToProps)(RegisterPage);
export { connectedRegisterPage as default };
App.jsx
import RegisterPage from '../RegisterPage';
console.log("RegisterPage", RegisterPage); // undefined
this gives no warning but display blank page in the browser
You have exported renderField as a default and imported as a named import. Correct it and it should work fine
import React from 'react';
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
export {renderField};

React/redux input values not showing

I am using react/redux with redux-form and for some reason the input values are not showing on my edit form.
I console.log my post and it shows that they are there, but for some reason it is not working. I will post code below.
edit component:
import React , { Component } from 'react';
import * as actions from '../../actions/posts_actions';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
class EditPost extends Component {
componentDidMount() {
const {id} = this.props.match.params;
this.props.getOnePost(id);
}
renderField(field) {
const { meta: {touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<label><strong>{field.label}:</strong></label>
<input
className="form-control"
type={field.type}
value={field.value}
{...field.input}
/>
<div className="text-help">
{ touched ? error : ''}
</div>
</div>
)
}
onSubmit(values) {
const {id} = this.props.match.params;
this.props.updatePost(values, id, () => {
this.props.history.push(`/posts/${id}`);
});
}
render() {
const {handleSubmit} = this.props;
const {post} = this.props;
if(!post) {
return <div> Loading... </div>;
}
console.log(post);
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Title"
name="title"
type="text"
value={post.title}
component={this.renderField}
/>
<Field
label="Content"
name="content"
type="text"
value={post.content}
component={this.renderField}
/>
<button type="submit" className="btn btn-success">Submit</button>
<Link to={`/posts/${post._id}`} className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if(!values.title) {
errors.title = "Enter a title!";
}
if(!values.content) {
errors.content = "Enter some content please!";
}
return errors;
}
function mapStateToProps({ posts }, ownProps) {
return { post: posts[ownProps.match.params.id] };
}
export default reduxForm({
validate,
form: 'editform'
})(connect(mapStateToProps, actions)(EditPost));
Instead of setting the value directly, pass an initialValues prop:
function mapStateToProps({ posts }, ownProps) {
const post = posts[ownProps.match.params.id]
return {
post,
initialValues: {
...post
}
};
}
remove value props from Fields in your form. So long as the properties on your Post object are the same as the name of the field you want to populate, you can just use object spread. If they are different, you'll have to map them appropriately
initialValues: {
contentField: post.content
}
<Field name="contentField" />
initial values have to be set before you use the reduxForm enhancer. Also, because I know it'll come up (and trust me, it always comes up eventually), if you want your form values to update if your model updates, you'll have add enableReinitialize: true to reduxForm's config

How to reset form on submission with Redux Form?

I'm creating a basic portfolio website, that has a Contact form section, where people can send me a message to my email via Formspree. I'm using Redux Forms for this and the form works, but I'd like to reset the form fields and add some kind of 'success' message on successful submit.
I've tried 2 methods: 1) calling this.props.resetForm() and 2) calling dispatch(reset('ContactForm'). Neither have worked.
Here is the code:
contact.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { sendMail } from '../actions';
class Contact extends Component {
renderField(field) {
const { meta: { touched, error } } = field;
const className = `form-group ${touched && error && 'has-error'}`;
return (
<div className={className}>
<label>{field.label}</label>
{field.type == 'text' ?
<input
className='form-control'
type={field.type}
{...field.input}
/> :
<textarea
className='form-control'
rows='5'
{...field.input}
/>}
<div className='help-block'>
{touched && error}
</div>
</div>
);
}
onSubmit(values) {
this.props.sendMail(values, () => {
this.props.resetForm();
});
}
render() {
const { handleSubmit } = this.props;
return (
<div id='contact'>
<h1>Contact</h1>
<p>Feel free to drop me a mail. Whether it's work-related or about coding, tech, entrepreneurship, travel, football or life in general. I'm always looking to connect with other people.</p>
<div id='contact-form'>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label='Name:'
name='name'
type='text'
component={this.renderField}
/>
<Field
label='Email:'
name='email'
type='text'
component={this.renderField}
/>
<Field
label='Message:'
name='message'
type='textarea'
component={this.renderField}
/>
<button
type='submit'
className='btn btn-primary'
>
Submit
</button>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {};
if (!values.name) {
errors.name = "Enter a name"
}
if (!values.email) {
errors.email = "Enter an email"
}
if (!values.message) {
errors.message = "Enter a message"
}
return errors;
}
export default reduxForm({
form: 'ContactForm',
validate
})(
connect(null, { sendMail })(Contact)
);
actions/index.js:
import axios from 'axios';
import { reset } from 'redux-form';
export const MAIL_SENT = 'MAIL_SENT';
const ROOT_URL='https://formspree.io/'
const EMAIL = 'xxxxxx#gmail.com'
export function sendMail(values) {
const request = axios.post(`${ROOT_URL}${EMAIL}`, values);
return {
type: MAIL_SENT,
payload: true
};
dispatch(reset('ContactForm'));
}
You need to add a constructor to the class contact.
class Contact extends Component {
constructor(props) {
super(props);
}
...
};
Putting dispatch(reset('ContactForm')) after return syntax, it would never get called. By the way, the action creator is supposed to be a pure function. Adding dispatch action to it is not a good idea.
Hope this help.
Use
this.props.reset()
Using this works on resetting the form after submit.

Resources