I am new to testing and I'm trying to test my app with react testing library. The first issue I ran into is testing functions that are inside of my components, like event handlers. I have found few examples where functions (event handlers) are passed in components as props but I don't want to do that if possible.
My Component:
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { login } from 'actions/auth';
import Wrapper from './styled/Wrapper';
import Button from './styled/Button';
import Title from './styled/Title';
import { FormWrapper, StyledForm, FormField } from './styled/Form'
import Input from './styled/Input';
import Link from './styled/Link';
const Login = (props) => {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = e => {
e.preventDefault();
props.login(email, password);
}
if (props.isAuthenticated) {
return <Redirect to='/' />
}
return (
<Wrapper color='#F2F2F2'>
<FormWrapper>
<Title>Log In</Title>
<StyledForm data-testid='auth-form' onSubmit={e => onSubmit(e)}>
<FormField>
<Input
placeholder='Email'
type='email'
name='email'
value={email}
onChange={e => onChange(e)}
required
/>
</FormField>
<FormField>
<Input
placeholder='Password'
type='password'
name='password'
value={password}
onChange={e => onChange(e)}
required
/>
</FormField>
<Button type='submit' marginTop='6%'>LOG IN</Button>
</StyledForm>
<p>Don't have an account? <Link to='/register'>Sign Up!</Link> </p>
</FormWrapper>
</Wrapper>
)
}
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps, { login })(Login);
My Test file:
import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import store from '../../store';
import Login from '../Login';
afterEach(cleanup);
const onChange = jest.fn();
test('<Login>', () => {
const { queryByTestId, getByPlaceholderText, getByText, debug } =
render(
<Provider store={store}>
<Router>
<Login />
</Router>
</Provider>);
expect(queryByTestId('auth-form')).toBeTruthy();
fireEvent.change(getByPlaceholderText('Email'), {
target: { value: '[email protected]' },
});
fireEvent.change(getByPlaceholderText('Password'), {
target: { value: 'testpassword' },
});
fireEvent.click(getByText('LOG IN'));
expect(onChange).toHaveBeenCalledTimes(1);
});
test output:
FAIL src/components/__tests__/Login.test.js
✕ <Login> (125 ms)
● <Login>
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
32 |
33 | fireEvent.click(getByText('LOG IN'));
> 34 | expect(onChange).toHaveBeenCalledTimes(1);
| ^
35 |
36 | });
at Object.<anonymous>.test (src/components/__tests__/Login.test.js:34:22)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.013 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
Is there a way to make onSubmit function accessible to the test without passing it trough as a prop?
The main point of the React Testing Library is that it's very easy to write tests that resembles on how users would use the application. This is what we want to achieve in order to give us full confidence and this is what's advocated in the industry as well. We write tests to raise confidence in our applications rather than to up the coverage.
Considering the above, you should not use toHaveBeenCalledTimes
instead, you should test the visual result of triggering the onChange
function. Based on the code you shared I don't know what is rendered upon successful login, but for our use case, let's say the text Welcome, User!
renders.
You could fire an event as you do now, and instead of testing with haveBeenCalled
, use the following:
expect(getByText('Welcome, User!').toBeInTheDocument();
This will give you full confidence as opposed to testing if a function was called which won't give you enough confidence because what if the implementation of the function is not correct?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With