使用Jasmine和Typescript测试Firebase的云功能 [英] Testing Cloud Functions for Firebase with Jasmine and Typescript

查看:50
本文介绍了使用Jasmine和Typescript测试Firebase的云功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在研究Google Cloud Functions,并用打字稿编写了一些基本的测试功能.

I'm currently investigating Google Cloud Functions and have some basic test functions written in typescript.

功能按预期工作,我现在正尝试使用Jasmine创建单元测试. (我没有按照文档使用Chai/sinon,因为我的项目其余部分都使用了茉莉花.)

The functions work as expected and I am now attempting to create unit tests using Jasmine. (I'm not using Chai/sinon as per the docs as the rest of my project uses jasmine).

我有两个问题 1)由于此错误,测试无法运行

I have two issues 1) the test does not run due to this error

抛出新的错误("Firebase配置变量不可用." + ^ 错误:Firebase配置变量不可用.请使用 最新版本的Firebase CLI来部署此功能

throw new Error('Firebase config variables are not available. ' + ^ Error: Firebase config variables are not available. Please use the latest version of the Firebase CLI to deploy this function

2)鉴于测试确实进行了,我不确定如何测试响应是否符合预期.

2) Given the test did run, I'm not sure how to test that the response is as expected.

索引文件

import * as functions from 'firebase-functions'

import { helloWorldHandler } from  './functions/hello-world';

export let helloWorld = functions.https.onRequest((req, res) => {
    helloWorldHandler(req, res);
});

正在测试的文件

export let helloWorldHandler = (request, response) => {
    response.send("Hello from Firebase Cloud!");
}

规格

import {} from 'jasmine';
import * as functions from 'firebase-functions'
import { helloWorldHandler } from './hello-world';
import * as endpoints from '../index';

describe('Cloud Functions : Hello World', () => {

    let configStub = {
        firebase: {
            databaseURL: "https://myProject.firebaseio.com",
            storageBucket: "myProject.appspot.com",
        }
    };

    it('should return correct message', () => {

        let spy = spyOn(functions, 'config').and.returnValue(configStub);

        const expected = 'Hello from Firebase Cloud!';
        // A fake request and response objects
        const req : any = {};
        const res : any = { };

        endpoints.helloWorld(req, res);

         //here test response from helloWorld is as expected

      });


});

推荐答案

如果您正在编写单元测试,则您不想测试第三方API.因此,目标应该是隔离您的代码逻辑并对其进行测试.端到端测试最适合对集成进行回归测试.

If you are writing unit tests, then you don't want to test third party APIs. Thus, the goal should be to isolate your code logic and test that. End-to-end tests are best suited for regression testing your integrations.

因此,这里的第一步是从图片中删除firebase-functions和数据库SDK之类的工具(在合理范围内).我通过将lib与函数逻辑分开来实现了这一点,

So the first step here would be to remove tools like firebase-functions and the Database SDKs from the picture (as much as this is reasonable). I accomplished this by separating my libs from the functions logic like so:

// functions/lib/http.js
exports.httpFunction = (req, res) => {
   res.send(`Hello ${req.data.foo}`);
};

// functions/index.js
const http = require('lib/http');
const functions = require('firebase-functions');

// we have decoupled the Functions invocation from the method
// so the method can be tested without including the functions lib!
functions.https.onRequest(http.httpFunction);

现在,我有一个很好的隔离逻辑,可以通过单元测试进行测试.我模拟了将传递给我的方法的所有参数,从而从图片中删除了第三方API.

Now I have nicely isolated logic that I can test via a unit test. I mock any arguments that would be passed into my methods, removing third party APIs from the picture.

这就是我在茉莉花中进行单元测试的样子:

So here's what my unit tests in Jasmine look like:

// spec/lib/http.spec.js
const http = require('../functions/lib/http');

describe('functions/lib/http', () => {
   expect('send to be called with "hello world"', () => {
      // first thing to do is mock req and res objects
      const req = {data: {foo: 'world'}};
      const res = {send: (s) => {});

      // now let's monitor res.send to make sure it gets called
      spyOn(res, 'send').and.callThrough();

      // now run it
      http.httpFunction(req, res);

      // new test it
      expect(res.send).toHaveBeenCalledWith("Hello world");
   });
});

测试第三方库有很多复杂性.最好的答案是尽早应用TDD/BDD原理并将抽象的第三方库抽象到可以轻松模拟的服务中.

There are a lot of complexities with testing third party libs. The best answer here to apply TDD/BDD principles early and abstract third party libs into services that can easily be mocked.

例如,如果我在功能内与Firebase Admin进行交互,那么我很容易最终想到一种具有很多第三方依赖性的方法来应对:

For example, if I were interacting with Firebase Admin within my functions, I could easily end up with a method that has lots of third party dependencies to contend with:

// functions/lib/http.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const env = require('./env');
const serviceAccount = require(env.serviceAccountPath);

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: `https://${env.dbUrl}.firebaseio.com`
});

exports.httpFunction = (req, res) => {
   let path = null;
   let data = null;

   // this is what I really want to test--my logic!
   if( req.query.foo ) {
      path = 'foo';
      data = 1;
   }

   // but there's this third library party coupling :(
   if( path !== null ) {
     let ref = admin.database.ref().child(path);
     return ref.set(data)
        .then(() => res.send('done'))
        .catch(e => res.status(500).send(e));
   }
   else {
      res.status(500).send('invalid query');
   }
};

要测试此示例,我必须包括并初始化Function以及Firebase Admin SDK,或者我必须找到一种模拟这些服务的方法.所有这些看起来都是一项艰巨的工作.相反,我可以使用DataStore抽象并利用它:

To test this example, I have to include and initialize Functions as well as the Firebase Admin SDK, or I have to find a way to mock those services. All of this looks like a pretty big job. Instead, I can have a DataStore abstraction and utilize that:

// An interface for the DataStore abstraction
// This is where my Firebase logic would go, neatly packaged
// and decoupled
class DataStore {
   set: (path, data) => {
      // This is the home for admin.database.ref(path).set(data);
   }
}

// An interface for the HTTPS abstraction
class ResponseHandler {
   success: (message) => { /* res.send(message); */ }
   fail: (error) => { /* res.status(500).send(error); */ } 
}

如果我现在添加从Functions流程中抽象逻辑的第一条原则,那么我的布局将如下所示:

If I now add in the first principle of abstracting my logic from the Functions process, then I have a layout like the following:

// functions/lib/http.js
exports.httpFunction = (query, responseHandler, dataStore) => {
   if( query.foo ) {
      return dataStore.set('foo', 1)
        .then(() => responseHandler.success())
        .catch(e => responseHandler.fail(e));
   }
   else {
      responseHandler.fail('invalid query');
   }
};

允许我编写一个更加优雅的单元测试:

Allowing me to write a unit test that's much more elegant:

// spec/lib/http
describe('functions/lib/http', () => {
   expect('is successful if "foo" parameter is passed', () => {
      // first thing to do is mock req and res objects
      const query = {foo: 'bar'};
      const responseHandler = {success: () => {}, fail: () => {});
      const dataStore = {set: () => {return Promise.resolve()}};

      // now let's monitor the results
      spyOn(responseHandler, 'success');

      // now run it
      http.httpFunction(query, responseHandler, dataStore);

      // new test it
      expect(res.success).toHaveBeenCalled();
   });
}); 

其余的代码也不错:

// functions/lib/firebase.datastore.js
// A centralized place for our third party lib!
// Less mocking and e2e testing!
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const serviceAccount = require(env.serviceAccountPath);

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: `https://${env.dbUrl}.firebaseio.com`
});

exports.set = (path, data) => {
  return admin.database.ref(path).set(data);
};

// functions/index.js
const functions = require('firebase-functions');
const dataStore = require('./lib/firebase.datastore');
const ResponseHandler = require('./lib/express.responseHandler');
const env = require('./env');
const http = require('./lib/http');

dataStore.initialize(env);

exports.httpFunction = (req, res) => {
   const handler = new ResponseHandler(res);
   return http.httpFunction(req.query, handler, dataStore);
};

更不用说从良好的BDD思维开始,我还以模块化的方式很好地隔离了项目的组件,当我们发现阶段2的所有范围爬升时,这将是很好的.:)

Not to mention that beginning with a good BDD mindset, I've also nicely isolated the components of my project in a modular way that's going to be nice when we find out about all the scope creep in phase 2. :)

这篇关于使用Jasmine和Typescript测试Firebase的云功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆