我有一系列功能,每个功能执行各种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调用为函数,然后再引用为属性。我相信这是我麻烦的根源。
答案 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)
如果嘲笑看起来很乏味,请不要。使用模拟器。
我认为这是处理测试中读写的相对较新的选项,因此我将其发布。这是一个快速演练。
$ curl -sL firebase.tools | bash
$ firebase init
const db = firebase.initializeApp(config).firestore()
if (location.hostname === "localhost") {
db.settings({
host: "localhost:8080",
ssl: false
});
}
$ firebase emulators:start
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")
})
})
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
答案 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(),
}
}
}));