首页 文章

使用Jasmine和Typescript测试Firebase的 Cloud 功能

提问于
浏览
0

我目前正在调查Google Cloud Functions,并使用typescript编写了一些基本的测试函数 .

这些函数按预期工作,我现在正尝试使用Jasmine创建单元测试 . (我不是按照文档使用Chai / sinon,因为我的项目的其余部分使用了jasmine) .

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

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

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

索引文件

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

      });


});

1 回答

  • 5

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

    所以这里的第一步是从图片中删除像 firebase-functions 和数据库SDK这样的工具(尽管这是合理的) . 我通过将我的libs与函数逻辑分开来实现这一点:

    // 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 .

    所以这是我在Jasmine中测试的单元的样子:

    // 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原则和抽象的第三方库应用到易于嘲笑的服务中 .

    例如,如果我在我的函数中与Firebase Admin进行交互,我可能很容易得到一个具有许多第三方依赖关系的方法:

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

    要测试此示例,我必须包含和初始化函数以及Firebase Admin SDK,或者我必须找到模拟这些服务的方法 . 所有这些看起来都非常重要 . 相反,我可以拥有DataStore抽象并利用它:

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

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

    // 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中的所有范围蠕变时,这将是很好的 . :)

相关问题