I used the dependancy injection approach on components and it meant I could mock and test methods without all the boilerplate.
For example, I have a form component that handles invites like so:
import React, { useEffect } from 'react';
import { Formik } from 'formik';
import { validations } from '../../helpers';
import { checkIfTeamExists } from '../helpers';
const Invite = ({ send, userEmail, handleTeamCreation, auth, db, dbWhere }) => {
useEffect(() => {
checkIfTeamExists(send, dbWhere);
}, []);
return (
<Formik
initialValues={{ email: '' }}
onSubmit={values =>
handleTeamCreation(userEmail, values.email, db, auth, send)
}
validate={validations}
render={props => (
<form onSubmit={props.handleSubmit} data-testid="form">
<input
type="email"
placeholder="Please enter your email."
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.email}
name="email"
/>
{props.errors.email && (
<p className="red" data-testid="error">
{props.errors.email}
</p>
)}
<button type="submit">Submit</button>
</form>
)}
/>
);
};
export default Invite;
The checkIfTeamExists
method relies on firebase auth and handleTeamCreation
method writes to the firestore.
When I referenced the component in its parent I instantiated it like so:
<Invite
send={send}
userEmail={value.user.user.email}
handleTeamCreation={handleTeamCreation}
auth={auth.sendSignInLinkToEmail}
db={db.collection('games')}
dbWhere={db.collection('games')
.where('player1', '==', value.user.user.email)
.get}
/>
Then, using react-testing-library
, in my tests I was able to mock things out with a simple jest.fn()
.
test('Invite form fires the send function on Submit ', async () => {
const handleTeamCreation = jest.fn();
const send = jest.fn();
const userEmail = '[email protected]';
const db = jest.fn();
const auth = jest.fn();
const dbWhere = jest.fn().mockResolvedValue([]);
const { getByPlaceholderText, getByTestId } = render(
<Invite
send={send}
userEmail={userEmail}
handleTeamCreation={handleTeamCreation}
auth={auth}
db={db}
dbWhere={dbWhere}
/>
);
const inputNode = getByPlaceholderText('Please enter your email.');
const email = '[email protected]';
fireEvent.change(inputNode, { target: { value: email } });
const formNode = getByTestId('form');
fireEvent.submit(formNode);
await wait(() => {
expect(handleTeamCreation).toHaveBeenCalledWith(
userEmail,
email,
db,
auth,
send
);
expect(handleTeamCreation).toHaveBeenCalledTimes(1);
});
});
and mocked the firestore where query in the same way.
test('Invite form must contain a valid email address', async () => {
const send = jest.fn();
const db = jest.fn();
const dbWhere = jest.fn().mockResolvedValue([]);
const { getByPlaceholderText, queryByTestId } = render(
<Invite send={send} db={db} dbWhere={dbWhere} />
);
expect(queryByTestId('error')).not.toBeInTheDocument();
const inputNode = getByPlaceholderText('Please enter your email.');
const email = 'x';
fireEvent.change(inputNode, { target: { value: email } });
await wait(() => {
expect(queryByTestId('error')).toHaveTextContent('Invalid email address');
});
});
This is very simple, but it works. It's also quite verbose but I thought a real use case would be more helpful than a contrived example. I hope this helps someone.