如果你正在編寫單元測試,那麼你不想要測試的第三方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思維開始,我也以模塊化的方式很好地隔離了我的項目的組件,當我們發現第二階段的所有範圍蠕變都會很好。 :)
請注意,您現在可以[通過本地運行函數](https://firebase.google.com/docs/functions/local-emulator)通過模擬器,這有助於測試。 – Kato