2017-09-14 65 views
0

我目前正在研究Google Cloud Functions,並且在打字稿中有一些基本的測試功能。用Jasmine和Typescript測試Firebase的雲功能

函數按預期工作,我現在試圖用Jasmine創建單元測試。 (我沒有按照文檔使用Chai/sinon,因爲我的項目的其餘部分使用了茉莉花)。

我有兩個問題 1)測試不運行,由於考慮到測試並運行這個錯誤

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),我不知道如何測試的響應預期。

索引文件

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 

     }); 


}); 
+0

請注意,您現在可以[通過本地運行函數](https://firebase.google.com/docs/functions/local-emulator)通過模擬器,這有助於測試。 – Kato

回答

3

如果你正在編寫單元測試,那麼你不想要測試的第三方API的文件。因此,目標應該是隔離你的代碼邏輯並測試它。端到端測試最適合對您的集成進行迴歸測試。

因此,這裏的第一步是從圖片中刪除諸如firebase-functions和Database SDK之類的工具(儘可能合理)。我完成這通過從功能的邏輯,像這樣分離我的庫:

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

那麼這裏就是在茉莉我的單元測試看起來像:

// 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原則和抽象的第三方庫應用到可輕易被嘲笑的服務中。

例如,如果我用火力地堡聯繫我的職權範圍內的互動,我可以很容易地結束了有大量的第三方的依賴,以抗衡的方法:

// 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,否則我必須找到一種方法來模擬這些服務。所有這些看起來都很不錯。相反,我可以有一個數據存儲的抽象和利用了:

// 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思維開始,我也以模塊化的方式很好地隔離了我的項目的組件,當我們發現第二階段的所有範圍蠕變都會很好。 :)

相關問題