I am learning reactjs form with hooks, now I would like to test form on submit using jest and enzyme.
here is my login component.
import React from 'react'
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
// ....api calLS
}
return (
<div>
<form onSubmit={handleSubmit} className="login">
<input type="email" id="email-input" name="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" id="password-input" name="password" value={password} onChange={e =>setPassword(e.target.value)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
export default Login
Here is is login.test.js file
it('should submit when data filled', () => {
const onSubmit = jest.fn();
const wrapper = shallow(<Login />)
const updatedEmailInput = simulateChangeOnInput(wrapper, 'input#email-input', 'test#gmail.com')
const updatedPasswordInput = simulateChangeOnInput(wrapper, 'input#password-input', 'cats');
wrapper.find('form').simulate('submit', {
preventDefault: () =>{}
})
expect(onSubmit).toBeCalled()
})
Unfortunately when I run npm test I get the following error
What do I need to do to solve this error or tutorial on testing form?
Issue here is you created a mock but it is not being consumed by the component you are testing.
const onSubmit = jest.fn(); // this is not being used by <Login />
A solution to this would be to mock the api calls you described on your code with the comment // ....api calLS and verify those are called successfully.
import { submitForm } from './ajax.js'; // the function to mock--called by handleSubmit
jest.mock('./ajax.js'); // jest mocks everything in that file
it('should submit when data filled', () => {
submitForm.mockResolvedValue({ loggedIn: true });
const wrapper = shallow(<Login />)
const updatedEmailInput = simulateChangeOnInput(wrapper, 'input#email-input', 'test#gmail.com')
const updatedPasswordInput = simulateChangeOnInput(wrapper, 'input#password-input', 'cats');
wrapper.find('form').simulate('submit', {
preventDefault: () =>{}
})
expect(submitForm).toBeCalled()
})
Useful links
very similar question
mocking modules
understanding jest mocks
Disclaimer: I am not experienced with the Enzyme framework.
Because your mocked function onSubmit is not binded to your form. You can't test it this way. If you gonna call some api onSubmit, you can mock this api and check if it was called (mockedApiFunction).
Related
I am having a custom hook to fetch data from the API. Invoking the custom hook outside handleSubmit works but inside handleSubmit it does not work.
const Login = () => {
const userRef = useRef();
const [user, setUser] = useState("");
const isUser = useLoginHook(user); **//// This works**
const handleSubmit = async (e) => {
e.preventDefault();
const isUser = useLoginHook(user); **///// This does not work**
};
return (
<>
<main className="App">
<form onSubmit={handleSubmit}>
<input
type="text"
id="username"
ref={userRef}
autoComplete="off"
onChange={(e) => setUser(e.target.value)}
value={user}
required
aria-invalid={validName ? "false" : "true"}
aria-describedby="uidnote"
onFocus={() => setUserFocus(true)}
onBlur={() => setUserFocus(false)}
/>
</form>
</main>
</>
);
};
I am getting error "React hook is called in function that is neither a React function or a custom React Hook function"
I am trying to use the event.preventDefault() method but I am continuously receiving error. It says that event has been deprecated.
I am making a Firebase SignUp form and I want to prevent the form from Submitting.
Here is the complete code.
import React from "react"
import styled from "styled-components"
import getFirebase from "../../firebase"
import useInput from "./useInput"
const SignUpForm = () => {
const firebaseInstance = getFirebase()
const email = useInput("")
const password = useInput("")
const signUp = async () => {
event.preventDefault()
try {
if (firebaseInstance) {
const user = await firebaseInstance
.auth()
.createUserWithEmailAndPassword(email.value, password.value)
console.log("user", user)
alert(`Welcome ${email.value}!`)
}
} catch (error) {
console.log("error", error)
alert(error.message)
}
}
event.preventDefault()
return (
<FormWrapper onSubmit={() => signUp()}>
<Title>Sign up</Title>
<Input placeholder="Email" {...email} />
<Input placeholder="Password" type="password" {...password} />
<Button type="submit">Sign up</Button>
</FormWrapper>
)
}
export default SignUpForm
And the useInput code:
import { useState } from "react"
const useInput = initialValue => {
const [value, setValue] = useState(initialValue)
const handleChange = event => {
setValue(event.target.value)
}
return {
value,
onChange: handleChange,
}
}
export default useInput
What that warning means is that the global variable window.event is deprecated. You can still access the event associated with an event handler, you just have to go about it the proper way - by using the parameter from the handler callback.
Change
<FormWrapper onSubmit={() => signUp()}>
to
<FormWrapper onSubmit={signUp}>
and then signUp's first parameter will be the event, and you'll be able to use it and call preventDefault on it as you're trying.
const signUp = async (event) => {
But don't put event.preventDefault() in your functional component's main body - that is, it shouldn't be here:
event.preventDefault()
return (
...
Only put it inside the signUp handler.
I am learning reactjs form with hooks, now I would like to test form on submit using jest and enzyme.
here is my login component.
import React from 'react'
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
// ....api calLS
}
return (
<div>
<form onSubmit={handleSubmit} className="login">
<input type="email" id="email-input" name="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" id="password-input" name="password" value={password} onChange={e =>setPassword(e.target.value)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
export default Login
Here is the login.test.js file
describe('my sweet test', () => {
it('clicks it', () => {
const wrapper = shallow(<Login />);
const updatedEmailInput = simulateChangeOnInput(wrapper, 'input#email-input', 'blah#gmail.com')
const updatedPasswordInput = simulateChangeOnInput(wrapper, 'input#password-input', 'death');
expect(updatedEmailInput.props().value).toEqual('blah#gmail.com');
expect(updatedPasswordInput.props().value).toEqual('death');
const instance = wrapper.instance()
const spy = jest.spyOn(instance, 'handleSubmit')
instance.forceUpdate();
const submitBtn = app.find('#sign-in')
submitBtn.simulate('click')
expect(spy).toHaveBeenCalled()
})
})
Unfortunately when I run npm test I get the following error.
What do I need to do to solve this error or can someone provide a tutorial on how to test a form submit?
In the documentation it's said that you cant use shallow.instance() for functional components
It will return null: https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/instance.html
There was also a previous answer on this topik
Enzyme instance() returns null
You can pass validated function handleSubmit to Login as a prop like there How to use jest.spyOn with React function component using Typescript
// Unit test
describe('SomeComponent' () => {
it('validates model on button click', () => {
const handleSubmit = jest.fn();
const wrapper = mount(
<Login handleSubmit={handleSubmit}/>
);
const instance = wrapper.instance();
const submitBtn = app.find('#sign-in')
submitBtn.simulate('click')
expect(handleSubmit).toHaveBeenCalled();
});
}
You need to call this test function handleSubmit in your login component either as a part of onSubmit or export whole onSubmit from upper components. Example login code with importing part of login function
import React from 'react'
function Login( {handleSubmit}) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const onSubmit = async (e) => {
if (handleSubmit) {
handleSubmit()
}
e.preventDefault();
// ....api calLS
}
return (
<div>
<form onSubmit={onSubmit} className="login">
<input type="email" id="email-input" name="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" id="password-input" name="password" value={password} onChange={e =>setPassword(e.target.value)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
export default Login
Example login code with importing of submit function
import React from 'react'
function Login( {handleSubmit}) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// handleSubmit is imported with props
return (
<div>
<form onSubmit={handleSubmit} className="login">
<input type="email" id="email-input" name="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" id="password-input" name="password" value={password} onChange={e =>setPassword(e.target.value)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
export default Login
this is my first week dealing with testing, and i get confused, i'm trying to test SignIn component, i have test the snapshot to ensure that mockup behavior not changing, then i want to test the submit behavior, here is my code:
signIn-component.jsx
import React, { useState } from 'react';
import FormInput from '../form-input/form-input.component';
import CustomButton from '../custom-button/custom-button.component';
import { connect } from 'react-redux';
import {
googleSignInStart,
emailSignInStart,
} from '../../redux/user/user.actions';
import './sign-in.styles.scss';
export const SignIn = ({ emailSignInStart, googleSignInStart }) => {
const [userCredentials, setCredentials] = React.useState({
email: '',
password: '',
});
const { email, password } = userCredentials;
const handleSubmit = async (event) => {
event.preventDefault();
emailSignInStart(email, password);
};
const handleChange = (event) => {
const { value, name } = event.target;
setCredentials({ ...userCredentials, [name]: value });
};
return (
<div className="sign-in">
<h2>I already have an account</h2>
<span>Sign in with your email and password</span>
<form onSubmit={handleSubmit}>
<FormInput
name="email"
type="email"
handleChange={handleChange}
value={email}
label="email"
required
/>
<FormInput
name="password"
type="password"
value={password}
handleChange={handleChange}
label="password"
required
/>
<div className="buttons">
<CustomButton type="submit"> Sign in </CustomButton>
<CustomButton
type="button"
onClick={googleSignInStart}
isGoogleSignIn
>
Sign in with Google
</CustomButton>
</div>
</form>
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
googleSignInStart: () => dispatch(googleSignInStart()),
emailSignInStart: (email, password) =>
dispatch(emailSignInStart({ email, password })),
});
export default connect(null, mapDispatchToProps)(SignIn);
sign.test.js
import { shallow , mount } from 'enzyme';
import React from 'react';
import toJson from 'enzyme-to-json';
import { SignIn } from '../sign-in.component';
describe('Sign In component', () => {
let wrapper;
const mockemailSignInStart = jest.fn();
const mockgoogleSignInStart = jest.fn();
const mockHandleSubmit = jest.fn();
beforeEach(() => {
wrapper = shallow(<SignIn
emailSignInStart={mockemailSignInStart}
googleSignInStart={mockgoogleSignInStart}/>
);
});
it('expect to render signIn component', () => {
expect(toJson(wrapper)).toMatchSnapshot();
});
it('expect call fn on submit', () => {
wrapper.find('form').simulate('submit');
expect(mockHandleSubmit).toBeCalled();
});
});
I have tried mount and render but always expect toBeCalled always return 0
I see 2 problems in your code:
1) I think this:
expect(mockHandleSubmit).toBeCalled();
should actually be
expect(mockemailSignInStart).toBeCalled();
because handleSubmit dispatches emailSignInStart which you mock with googleSignInStart.
2) You should pass some argument to your simulate('submit') or the handleSubmit will throw an error when calling event.preventDefault();. For instance you can just use:
wrapper.find("form").simulate("submit", { preventDefault: jest.fn() });
I'm new to React and testing in general so forgive the naivety of the question. I have a React form component which onChance on the inputs runs a function handleChange. Tried to test it with Jest but can't make it work.
Here's the Login component:
class Login extends React.Component {
constructor() {
super();
this.state = {username: '', password: ''}
this.disableSubmit = this.disableSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
return(
<div className="login">
<form>
<h3 className="login__title">LOGIN</h3>
<div className="input-group">
<input onChange={this.handleChange} value={this.state.username} className="form-control login__input username" type="text" placeholder="user name" name={'username'} autoFocus/>
</div>
<div className="input-group">
<input onChange={this.handleChange} value={this.state.password} className="form-control login__input password" type="password" placeholder="password" name={'password'}/>
</div>
<div>
<button className="btn btn-primary btn-block login__button" type="submit">Login</button>
</div>
</form>
</div>
)
}
}
export default Login;
Here's my test:
import React from 'react'
import { shallow, mount } from 'enzyme'
import { shallowToJson } from 'enzyme-to-json'
import {Login} from '../../../src/base/components/index'
describe('Given the Login component is rendered', () => {
describe('Snapshots', () => {
let component
beforeEach(() => {
component = shallow(<Login />)
})
it('should be as expected', () => {
expect(shallowToJson(component)).toMatchSnapshot()
})
})
})
test('Submitting the form should call handleSubmit', () => {
const startState = {username: ''};
const handleChange = jest.fn();
const login = mount(<Login />);
const userInput = login.find('.username');
userInput.simulate('change');
expect(handleChange).toBeCalled();
})
The snapshot test passes fine, but in this last attempt my function test fails with:
TypeError: Cannot read property 'target' of undefined
Guess I need to pass something to the function? Bit confused!
Thanks in advance for your help.
UPDATE:
changed the test as follows but test fails with: expect(jest.fn()).toBeCalled() Expected mock function to have been called.
test updated:
test('Input should call handleChange on change event', () => {
const login = mount(<Login />);
const handleChange = jest.spyOn(login.instance(), 'handleChange');
const userInput = login.find('.username');
const event = {target: {name: "username", value: "usertest"}};
userInput.simulate('change', event);
expect(handleChange).toBeCalled();
})
Yes, you'll need to pass an event object to you simulate function.
const event = {target: {name: "special", value: "party"}};
element.simulate('change', event);
EDIT: Oh, and you'll also need to do something like:
jest.spyOn(login.instance(), 'handleChange')
but that's unrelated to your error
Found the solution in here: Enzyme simulate an onChange event
test('Input should call handleChange on change event', () => {
const event = {target: {name: 'username', value: 'usertest'}};
const login = mount(<Login />);
const handleChange = jest.spyOn(login.instance(), 'handleChange');
login.update(); // <--- Needs this to force re-render
const userInput = login.find('.username');
userInput.simulate('change', event);
expect(handleChange).toBeCalled();
})
It needed this login.update(); in order to work!
Thank everyone for your help!
handleChange isn't currently being mocked. A couple of approaches:
Pass change event handler as prop to Login component.
<div className="input-group">
<input
onChange={this.props.handleChange}
value={this.state.username}
className="form-control login__input username"
type="text"
placeholder="user name"
name={'username'}
autoFocus
/>
</div>
login.spec.js
...
const handleChange = jest.fn();
const login = mount(<Login handleChange={handleChange}/>);
...
Replace handleChange with the mock function.
...
const handleChange = jest.fn();
const login = mount(<Login />);
login['handleChange'] = handleChange // replace instance
...
expect(handleChange).toBeCalled();
Use jest spyOn to create a mock function that wraps the original function.
...
const handleChange = jest.spyOn(object, 'handleChange') // will call the original method
expect(handleChange).toBeCalled();
Replace handleChange on the Login component with a mock function.
...
const handleChange = jest.spyOn(object, 'handleChange').mock // will call the original method
expect(handleChange).toBeCalled();