您如何使用Jest模拟Firebase Firestore方法?

时间:2018-08-27 17:01:30

标签: javascript firebase google-cloud-firestore jestjs

我有一系列功能,每个功能执行各种Firestore交互。如何使用Jest模拟这些Firestore调用?我想避免使用图书馆。

当我使用jest.mock("firebase/app")jest.mock("firebase/firestore")以及其他变体时,我得到的是null TypeErrors或表示我仍在引用实际导入而不是模拟的错误:Error: ... make sure you call initializeApp()

例如,我要测试的一个简单功能:

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);
};

请注意如何照常导入firebase,然后导入firestore的副作用。还要注意,如何首先将Firestore调用为函数,然后再引用为属性。我相信这是我麻烦的根源。

7 个答案:

答案 0 :(得分:7)

距离该问题的任何活动已经有一段时间了,但是在线上仍然没有多少资料,这是我的解决方案:

this.setState({ reloadTableCustom: false });

这是一个示例用法:

export default class FirestoreMock {
  constructor () {
    // mocked methods that return the class
    this.mockCollection = jest.fn(() => this)
    this.mockWhere = jest.fn(() => this)
    this.mockOrderBy = jest.fn(() => this)

    // methods that return promises
    this.mockAdd = jest.fn(() => Promise.resolve(this._mockAddReturn))
    this.mockGet = jest.fn(() => Promise.resolve(this._mockGetReturn))

    // methods that accepts callbacks
    this.mockOnSnaptshot = jest.fn((success, error) => success(this._mockOnSnaptshotSuccess))

    // return values
    this._mockAddReturn = null
    this._mockGetReturn = null
    this._mockOnSnaptshotSuccess = null
  }

  collection (c) {
    return this.mockCollection(c)
  }

  where (...args) {
    return this.mockWhere(...args)
  }

  orderBy (...args) {
    return this.mockOrderBy(...args)
  }

  add (a) {
    return this.mockAdd(a)
  }

  get () {
    return this.mockGet()
  }

  onSnapshot (success, error) {
    return this.mockOnSnaptshot(success, error)
  }

  set mockAddReturn (val) {
    this._mockAddReturn = val
  }

  set mockGetReturn (val) {
    this._mockGetReturn = val
  }

  set mockOnSnaptshotSuccess (val) {
    this._mockOnSnaptshotSuccess = val
  }

  reset () {
    // reset all the mocked returns
    this._mockAddReturn = null
    this._mockGetReturn = null
    this._mockOnSnaptshotSuccess = null

    // reset all the mocked functions
    this.mockCollection.mockClear()
    this.mockWhere.mockClear()
    this.mockOrderBy.mockClear()
    this.mockAdd.mockClear()
    this.mockGet.mockClear()
  }
}

如果有任何兴趣,我可以打包import FirestoreMock from '../test_helpers/firestore.mock' import firebase from 'firebase/app' import 'firebase/firestore' describe('The Agreement model', () => { const firestoreMock = new FirestoreMock() beforeEach(() => { firebase.firestore = firestoreMock firestoreMock.reset() }) it('does something', (done) => { firestoreMock.mockAddReturn = { id: 'test-id' } firebase.firestore.collection('foobar') .add({foo: 'bar'}) .then(res => { expect(firestoreMock.mockCollection).toBeCalledWith('foobar') expect(firestoreMock.mockAdd).toBeCalledWith({foo: 'bar'}) expect(res.id).toEqual('test-id') done() }) .catch(done) }) }) 实现,以便可以轻松共享

Teo

答案 1 :(得分:3)

如果嘲笑看起来很乏味,请不要。使用模拟器。

我认为这是处理测试中读写的相对较新的选项,因此我将其发布。这是一个快速演练。

  1. 下载firebase CLI工具。
$ curl -sL firebase.tools | bash
  1. 如果尚未在项目中初始化Firebase,请执行以下操作。除非您知道需要其他人,否则只需选择firestore即可开始。
$ firebase init
  1. 配置您的firestore实例以指向仿真器(您应该能够模拟db来重定向到仿真器的实例,但是这种方式也将允许您在开发环境中读取/写入仿真器)。
const db = firebase.initializeApp(config).firestore()
if (location.hostname === "localhost") {
  db.settings({
    host: "localhost:8080",
    ssl: false
  });
}
  1. 启动模拟器。还有一个命令可以在shell命令期间运行仿真器,您可以根据需要将其添加到测试套件npm脚本中。
$ firebase emulators:start
  1. 测试使用Firestore的东西。
  describe('New city', () => {
    it('should create a new city in firestore', async () => {
      await db.collection('cities').doc('Seattle').set({ state: "WA" })
      const city = await db.collection('cities').doc("Seattle").get()

      expect(city.data()['population']).toEqual("WA")
    })
  })
  1. 可选:创建一个数据库清理功能,该功能使用模拟器的其余端点在测试之间删除数据。
async function cleanFirestore() {
  const Http = new XMLHttpRequest();
  const url = "http://localhost:8080/emulator/v1/projects/<YOUR-PROJECT-ID>/databases/(default)/documents"

  Http.open("DELETE", url);
  Http.send();

  return new Promise((resolve, reject) => {
    setTimeout(reject, 2000)
    Http.onreadystatechange = resolve
  })
}

有关Google的模拟器演练指南,请执行以下操作: https://google.dev/pathways/firebase-emulators

文档: https://firebase.google.com/docs/emulator-suite

答案 2 :(得分:2)

这是我找到的解决方案。网上没有太多有关此的信息,所以我希望它能对某人有所帮助。

诀窍是创建一个模拟函数的链接API,并将其设置在firebase对象上,而不是导入和模拟firestore。下面的示例使我可以测试上述示例函数以及doc().get() Promise。

const docData = { data: "MOCK_DATA" };
const docResult = {
  // simulate firestore get doc.data() function
  data: () => docData
};
const get = jest.fn(() => Promise.resolve(docResult));
const set = jest.fn();
const doc = jest.fn(() => {
  return {
    set,
    get
  };
});
const firestore = () => {
  return { doc };
};
firestore.FieldValue = {
  serverTimestamp: () => {
    return "MOCK_TIME";
  }
};

export { firestore };

我在所有测试都先运行的文件中声明它(请参阅文档),然后将其导入并在我的测试文件中使用,如下所示:

import firebase from "firebase/app";
import { firestore } from "../setupTests";
firebase.firestore = firestore;

describe("setDocData", () => {
  const mockData = { fake: "data" };
  beforeEach(() => {
    jest.clearAllMocks();
    setDocData("fakeDocID", mockData);
  });

  it("writes the correct doc", () => {
    expect(firestore().doc).toHaveBeenCalledWith("docs/fakeDocID");
  });

  it("adds a timestamp, and writes it to the doc", () => {
    expect(firestore().doc().set).toHaveBeenCalledWith({
      created: "MOCK_TIME",
      fake: "data"
    });
  });
});

答案 3 :(得分:1)

这是我嘲笑Firebase的方式。

'use strict'

const collection = jest.fn(() => {
  return {
    doc: jest.fn(() => {
      return {
        collection: collection,
        update: jest.fn(() => Promise.resolve(true)),
        onSnapshot: jest.fn(() => Promise.resolve(true)),
        get: jest.fn(() => Promise.resolve(true))
      }
    }),
    where: jest.fn(() => {
      return {
        get: jest.fn(() => Promise.resolve(true)),
        onSnapshot: jest.fn(() => Promise.resolve(true)),
      }
    })
  }
});

const Firestore = () => {
  return {
    collection
  }
}

Firestore.FieldValue = {
  serverTimestamp: jest.fn()
}

export default class RNFirebase {

  static initializeApp = jest.fn();

  static auth = jest.fn(() => {
    return {
      createUserAndRetrieveDataWithEmailAndPassword: jest.fn(() => Promise.resolve(true)),
      sendPasswordResetEmail: jest.fn(() => Promise.resolve(true)),
      signInAndRetrieveDataWithEmailAndPassword: jest.fn(() => Promise.resolve(true)),
      fetchSignInMethodsForEmail: jest.fn(() => Promise.resolve(true)),
      signOut: jest.fn(() => Promise.resolve(true)),
      onAuthStateChanged: jest.fn(),
      currentUser: {
        sendEmailVerification: jest.fn(() => Promise.resolve(true))
      }
    }
  });

  static firestore = Firestore;

  static notifications = jest.fn(() => {
    return {
        onNotification: jest.fn(),
        onNotificationDisplayed: jest.fn(),
        onNotificationOpened: jest.fn()
    }
  });

  static messaging = jest.fn(() => {
    return {
        hasPermission: jest.fn(() => Promise.resolve(true)),
        subscribeToTopic: jest.fn(),
        unsubscribeFromTopic: jest.fn(),
        requestPermission: jest.fn(() => Promise.resolve(true)),
        getToken: jest.fn(() => Promise.resolve('RN-Firebase-Token'))
    }
  });

  static storage = jest.fn(() => {
    return {
      ref: jest.fn(() => {
        return {
          child: jest.fn(() => {
            return {
              put: jest.fn(() => Promise.resolve(true))
            }
          })
        }
      })
    }
  })

}

答案 4 :(得分:1)

我在组件上使用了依赖注入方法,这意味着我可以在没有所有样板的情况下模拟和测试方法。

例如,我有一个处理邀请的表单组件,如下所示:

componentDidUpdate

componentDidMount(prevProps, prevState) { if (this.state.showSnackbar && !prevState.showSnackbar) { this.setState({ showSnackbar: false }) } } 方法依赖于Firebase身份验证,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; 方法将写入Firestore。

当我在其父级中引用该组件时,我将其实例化为:

checkIfTeamExists

然后,在测试中使用handleTeamCreation,我可以用一个简单的<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} /> 来模拟一切。

react-testing-library

并以相同的方式模拟了Firestore的查询。

jest.fn()

这很简单,但是可以。它也很冗长,但是我认为一个真实的用例比一个人为的例子更有帮助。我希望这对某人有帮助。

答案 5 :(得分:0)

我发现模拟导入效果很好。我在测试中添加了此代码,在上方渲染了导入“ firebase / app”的组件

jest.mock('firebase/app', () => ({
  __esModule: true,
  default: {
    apps: [],
    initializeApp: () => {},
    auth: () => {},
  },
}));

答案 6 :(得分:0)

我在其中一项功能中使用firebase.firestore.FieldValue.serverTimestamp()

import firestore from '@react-native-firebase/firestore';
import firebase from  '@react-native-firebase/app';

export function getUserCreateObject() {
    return {
        property1: {
          property2: {
            value1: true,
            last_updated_date: firebase.firestore.FieldValue.serverTimestamp(),
          },
          //the rest of the JSON object
        },
      };
}

要对此进行模拟,我在package.json中引用了一个jest.setup.js文件:

"jest": {
    "preset": "react-native",
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "transform": {
      "\\.js$": "<rootDir>/node_modules/babel-jest"
    },
    "transformIgnorePatterns": [
      "/node_modules/(?!(jest-)?react-native|@react-native-firebase/auth|@react-native-firebase/app|@react-native-firebase/app-types)"
    ],
    "setupFiles": [
      "./jest/jest.setup.js"
    ],
    "coveragePathIgnorePatterns": [
      "/node_modules/",
      "/jest"
    ]
  }

在jest.setup.js中,我这样做:

jest.mock('@react-native-firebase/app', () => ({
    firestore: {
      FieldValue: {
        serverTimestamp: jest.fn(),
      }
    }
}));