How do you mock Firebase Firestore methods using J

2020-07-10 07:49发布

I have a series of functions, each performing various firestore interactions. How do I use Jest to mock these firestore calls? I would like to avoid using a library.

When I use jest.mock("firebase/app") and jest.mock("firebase/firestore") and other variations, I either get null TypeErrors, or errors indicating I am still referencing the actual import and not the mock: Error: ... make sure you call initializeApp().

For example, a simple function I want to test:

import firebase from "firebase/app";
import "firebase/firestore";

export const setDocData = (id, data) => {
  const newDoc = {
    created: firebase.firestore.FieldValue.serverTimestamp(),
    ...data
  };
  firebase
    .firestore()
    .doc("docs/" + id)
    .set(newDoc);
};

Notice how firebase is imported as usual, then firestore is imported a side effect. Also notice how firestore is called first as a function, then later referenced as a property. I believe this is the source of my trouble.

7条回答
可以哭但决不认输i
2楼-- · 2020-07-10 08:23

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 = 'ex@mple.com';
  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 = 'me@gmail.com';
  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.

查看更多
登录 后发表回答