如何使用用于 JS 开发的 firebase Emulator 设置 firebase firestore 和云功能测试套件

Posted

技术标签:

【中文标题】如何使用用于 JS 开发的 firebase Emulator 设置 firebase firestore 和云功能测试套件【英文标题】:How to setup a firebase firestore and cloud function test suit with firebase Emulator for JS development 【发布时间】:2019-10-07 17:36:23 【问题描述】:

根据 firebase 团队的以下 google I/O (2019) 帖子,新的模拟器允许我们结合 firebase/database 和云功能来完全模拟我们的 firebase 服务器代码。这也应该意味着我们应该能够为它编写测试。

我们正在发布一个全新的 Cloud Functions 模拟器,它还可以 与 Cloud Firestore 模拟器通信。所以如果你想建立 触发 Firestore 文档更新并写入的函数 数据返回数据库,您可以对整个流程进行编码和测试 本地在您的笔记本电脑上(来源:Firebase Blog Entry)

我可以找到多个资源来查看/描述每个单独的模拟,但不能全部放在一起

    Unit Testing Cloud Function Emulate Database writes Emulate Firestore writes

【问题讨论】:

【参考方案1】:

要为允许您模拟读/写和设置测试数据的云功能设置测试环境,您必须执行以下操作。请记住,这确实模拟/触发了云功能。因此,在写入 firestore 后,您需要稍等片刻,直到云函数完成写入/处理,然后才能读取断言数据。

可以在此处找到包含以下代码的示例 repo:https://github.com/BrandiATMuhkuh/jaipuna-42-firebase-emulator。

前提条件

我假设此时您已经设置了一个 firebase 项目,其中包含一个函数文件夹和 index.js。测试稍后将位于functions/test 文件夹中。如果您没有项目设置,请使用firebase init 设置项目。

安装依赖项

首先将以下依赖项添加/安装:mocha@firebase/rules-unit-testingfirebase-functions-testfirebase-functionsfirebase-adminfirebase-toolsfunctions/package.json 而不是根文件夹。

cd "YOUR-LOCAL-EMULATOR"/functions (for example cd C:\Users\User\Documents\FirebaseLocal\functions)

npm install --save-dev mocha
npm install --save-dev firebase-functions-test
npm install --save-dev @firebase/rules-unit-testing
npm install firebase-admin
npm install firebase-tools

替换所有jaipuna-42-firebase-emulator 名称

使用自己的project-id 非常重要。它必须是您自己项目的project-id,并且必须存在。假身份证是不行的。因此,在下面的代码中搜索所有 jaipuna-42-firebase-emulator 并将其替换为您的 project-id

index.js 示例云函数

// functions/index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");

// init the database
admin.initializeApp(functions.config().firebase);
let fsDB = admin.firestore();

const heartOfGoldRef = admin
    .firestore()
    .collection("spaceShip")
    .doc("Heart-of-Gold");

exports.addCrewMemeber = functions.firestore.document("characters/characterId").onCreate(async (snap, context) => 
    console.log("characters", snap.id);

    // before doing anything we need to make sure no other cloud function worked on the assignment already
    // don't forget, cloud functions promise an "at least once" approache. So it could be multiple
    // cloud functions work on it. (FYI: this is called "idempotent")

    return fsDB.runTransaction(async t => 
        // Let's load the current character and the ship
        const [characterSnap, shipSnap] = await t.getAll(snap.ref, heartOfGoldRef);

        // Let's get the data
        const character = characterSnap.data();
        const ship = shipSnap.data();

        // set the crew members and count
        ship.crew = [...ship.crew, context.params.characterId];
        ship.crewCount = ship.crewCount + 1;

        // update character space status
        character.inSpace = true;

        // let's save to the DB
        await Promise.all([t.set(snap.ref, character), t.set(heartOfGoldRef, ship)]);
    );
);


mocha 测试文件 index.test.js

// functions/test/index.test.js


// START with: yarn firebase emulators:exec "yarn test --exit"
// important, project ID must be the same as we currently test

// At the top of test/index.test.js
require("firebase-functions-test")();

const assert = require("assert");
const firebase = require("@firebase/testing");

// must be the same as the project ID of the current firebase project.
// I belive this is mostly because the AUTH system still has to connect to firebase (googles servers)
const projectId = "jaipuna-42-firebase-emulator";
const admin = firebase.initializeAdminApp( projectId );

beforeEach(async function() 
    this.timeout(0);
    await firebase.clearFirestoreData( projectId );
);

async function snooz(time = 3000) 
    return new Promise(resolve => 
        setTimeout(e => 
            resolve();
        , time);
    );


it("Add Crew Members", async function() 
    this.timeout(0);

    const heartOfGold = admin
        .firestore()
        .collection("spaceShip")
        .doc("Heart-of-Gold");

    const trillianRef = admin
        .firestore()
        .collection("characters")
        .doc("Trillian");

    // init crew members of the Heart of Gold
    await heartOfGold.set(
        crew: [],
        crewCount: 0,
    );

    // save the character Trillian to the DB
    const trillianData =  name: "Trillian", inSpace: false ;
    await trillianRef.set(trillianData);

    // wait until the CF is done.
    await snooz();

    // check if the crew size has change
    const heart = await heartOfGold.get();
    const trillian = await trillianRef.get();

    console.log("heart", heart.data());
    console.log("trillian", trillian.data());

    // at this point the Heart of Gold has one crew member and trillian is in space
    assert.deepStrictEqual(heart.data().crewCount, 1, "Crew Members");
    assert.deepStrictEqual(trillian.data().inSpace, true, "In Space");
);


运行测试

要一次性运行测试和模拟器,我们导航到functions 文件夹并写入yarn firebase emulators:exec "yarn test --exit"。此命令也可以在您的 CI 管道中使用。或者你也可以改用npm test

如果一切正常,您应该会看到以下输出

  √ Add Crew Members (5413ms)

  1 passing (8S)

【讨论】:

那你什么时候initializeApp?从您的示例中不清楚。我希望它在 index.js 中运行。你能澄清一下吗? 上周,我去了马德里的 Firebase 峰会,与 Firebase 团队交谈。他们告诉如何正确设置测试服。我已经相应地更新了上面的代码。 你也可以在这里找到一个codelab:google.dev/playlists/firebase-emulators Point 5 Test Drive Complex Firebase Functions是你感兴趣的 你为什么用this.timeout(0); 因为mocha的默认超时时间太短了,所以你甚至无法启动模拟器等,在它失败之前。就我而言,我只是停用了超时。在生产中,您可以将其设置为更真实的东西。例如 10 秒左右。【参考方案2】:

对于任何在测试 Firestore 触发器方面遇到困难的人,我制作了一个示例存储库,希望能对其他人有所帮助。

https://github.com/benwinding/example-jest-firestore-triggers

它使用 jest 和本地 firebase 模拟器。

【讨论】:

嗨 Ben,你能帮我解决这个问题吗***.com/questions/66964566/…?我正在努力使用模拟器测试 Firestore 触发器。也许你可以帮忙 当然@sarah我会试试

以上是关于如何使用用于 JS 开发的 firebase Emulator 设置 firebase firestore 和云功能测试套件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Firebase 作为具有生成密钥(JS)的关系数据库?

使用 firebase 模拟器时如何共享指向 firebase 开发环境的链接?

如何使用firebase auth限制对next.js中页面的访问?

Firebase:如何调用 https.onCall 函数 node.js?

带有 Firebase (FCM) 推送通知的 Node js

如何正确地将 getStaticProps 用于 firebase?