Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test components internal functions with React testing library

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?

like image 422
pindimindi Avatar asked Nov 07 '22 03:11

pindimindi


1 Answers

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?

like image 85
anddak Avatar answered Nov 12 '22 16:11

anddak