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
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'm trying to make a little login screen with functional React. I have an input button that I want to click and have the login post happen. For the life of me, I can't get the handler to fire. loginPressed just won't get called. I'm sure it's something easy that I'm overlooking.
import * as React from 'react';
import axios from 'axios'
export default function Login() {
const [email, setEmail] = React.useState([]);
const [password, setPassword] = React.useState([]);
const loginPressed = () => {
var body = {
'email': email,
'password': password
}
axios.post('login', body)
.then(response => {
})
}
return (
<div>
<p>Username:</p>
<p><input type="text" name="email" onChange={(e) => {setEmail(e.target.value)}}/></p>
<p>Password:</p>
<p><input type="password" name="password" onChange={(e) => {setPassword(e.target.value)}}/></p>
<p>
<input type='button' value='Login' onClick={loginPressed}/>
</p>
</div>
);
}
You should use form with onSubmit={loginPressed}. Instead of input use button html element with type of submit.
this is a sign-in page I created using React & styled components. I was trying to clear the input field values "onSubmit" but for some issue it does not work. Does anyone knows how should I clear the inputs with the button click ? Thank you!
import React, { useState } from "react";
import {
Container,
Form,
FormButton,
FormContent,
FormH1,
FormInput,
FormLabel,
FormWrap,
Icon,
Text,
} from "./SigninElements";
const SignIn = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e) => {
setPassword(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(email, password);
setEmail("");
setPassword("");
};
return (
<Container>
<FormWrap>
<Icon to="/">Sushi Finder</Icon>
<FormContent>
<Form action="POST" onSubmit={handleSubmit}>
<FormH1>Sign in to your account</FormH1>
<FormLabel htmlFor="for">Email</FormLabel>
<FormInput type="email" required onChange={handleEmailChange} />
<FormLabel htmlFor="for">Password</FormLabel>
<FormInput
type="password"
required
onChange={handlePasswordChange}
/>
<FormButton type="submit">Continue</FormButton>
<Text>Forgot Password</Text>
</Form>
</FormContent>
</FormWrap>
</Container>
);
};
export default SignIn;
What you have are not controlled inputs, as Jayce444 points out. If you want to clear the inputs then you can do 1 of 2 things:
Actually make your inputs controlled by setting the value prop accordingly. Then the state updates in the submit handler will clear the state and be reflected in the inputs' values.
<FormInput
type="email"
required
onChange={handleEmailChange}
value={email}
/>
...
<FormInput
type="password"
required
onChange={handlePasswordChange}
value={password}
/>
Leave them uncontrolled and clear the inputs via the onSubmit event in the handler. Provide an id for each input to access in the submit handler and reset their values.
const handleSubmit = (e) => {
e.preventDefault();
console.log(email, password);
setEmail("");
setPassword("");
e.target.email.value = "";
e.target.password.value = "";
};
...
<FormInput
type="email"
required
onChange={handleEmailChange}
id="email"
/>
...
<FormInput
type="password"
required
onChange={handlePasswordChange}
id="password"
/>
Its a uncontrolled component. That means single way approach you pass the value from input. But not possible to change the input via Dom.
So better use controlled component value={email}. So this will reset
the value via state.
And i have post solution for this uncontrolled component via key updated method.
Example Codesanbox
import React, { useState } from "react";
import {
Container,
Form,
FormButton,
FormContent,
FormH1,
FormInput,
FormLabel,
FormWrap,
Icon,
Text,
} from "./SigninElements";
const keyGen = () => 'key'+Math.round(Math.random()*10000000000000);
const SignIn = () => {
const [key, setKey] = useState(keyGen());
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e) => {
setPassword(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(email, password);
setEmail("");
setPassword("");
setKey(keyGen())
};
return (
<Container>
<FormWrap>
<Icon to="/">Sushi Finder</Icon>
<FormContent key={key}>
<Form action="POST" onSubmit={handleSubmit}>
<FormH1>Sign in to your account</FormH1>
<FormLabel htmlFor="for">Email</FormLabel>
<FormInput type="email" required onChange={handleEmailChange} />
<FormLabel htmlFor="for">Password</FormLabel>
<FormInput
type="password"
required
onChange={handlePasswordChange}
/>
<FormButton type="submit">Continue</FormButton>
<Text>Forgot Password</Text>
</Form>
</FormContent>
</FormWrap>
</Container>
);
};
export default SignIn;
You can also use useRef() to control those input elements, which includes clearing them. Declare them for each input like so:
const input1 = React.useRef();
const input2 = React.useRef();
Then use the ref prop of the FormInput:
<FormInput
ref={input1}
type="password"
required
onChange={handlePasswordChange}
id="password"
/>
And then you can clear them in your handleSubmit function like so:
input1.current.value = ""
The beauty of controlled components are that you can control them outside of the DOM. All you simply need to do is set the value state variable to null.
const [myValue, setMyValue] = React.useState(null)
const clearValue = () => {
setMyValue(null)
}
const handleOnChange = (event) => {
setMyValue(event.target.value)
}
return <>
<FormInput
onChange={handleOnChange}
value={myValue}
/>
<Button onClick={clearValue}>Clear</Button>
</>
Since the clear button calls clearValue on click and the clearValue sets myValue to null on click, this should clear the value of your input. Controlled components are driven by a variable, which is updated by what the user types. Uncontrolled components are only driven by what the user types.
To use a controlled component you are required to pass your state variable back to the component though.
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).
I'm trying to create a user authentication page using React and Apollo. I'm getting that my event is undefined for onClick event for the following code:
const Auth = props => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const onSubmitHandler = async (login, e) => {
e.preventDefault()
const authResults = await login()
props.history.push('/')
}
return (
<Mutation
mutation={LOGIN}
variables={{ email, password }}
>
{(login, {data, error, loading}) => {
if(loading) return <div>...loading</div>
if(error) return <div>Error</div>
return (
<form onSubmit={onSubmitHandler}>
<fieldset>
<label htmlFor="email">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={e => {
setEmail(e.target.value)
}}
/>
</fieldset>
<fieldset>
<label htmlFor="password">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={e => {
setPassword(e.target.value)
}}
/>
</fieldset>
<button>Login</button>
</form>
)
}}
</Mutation>
)
I tried including the variables at the point of invocation as following:
<form onSubmit={(login, e) => onSubmitHandler(login, e)}>
But, to no avail.
Edit: The initial issue has been resolved, but when I refactored the code to utilize react-apollo-hooks, I keep getting the following errors:
Variable "$email" of required type "String!" was not provided
Variable "$password" of required type "String!" was not provided
My refactored codes are as follows:
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const variables = {
data: { email, password }
}
const login = useMutation(LOGIN_MUTATION, {
variables
})
const onSubmitHandler = async (login, e) => {
e.preventDefault()
const authResults = await login()
localStorage.setItem(AUTH_TOKEN, authResults.data.login.token);
props.history.push('/')
}
The graphql mutation
const LOGIN_MUTATION = gql`
mutation Login($email: String!, $password: String!) {
login(data: {
email: $email, password: $password
}
){
token
user {
id
}
}
}
`
My schema
login(data: LoginUserInput!): AuthPayload!