From 0e032649c88da825b193830bb7704377b78c8ac6 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Thu, 2 Mar 2017 19:53:25 -0800 Subject: [PATCH] Add unit tests for makeUppercase and sendMessage. Change-Id: I4184e2ce55149485893aa5459e87d4bd5d5324b6 --- quickstarts/uppercase/functions/package.json | 11 +- quickstarts/uppercase/functions/test/test.js | 135 +++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 quickstarts/uppercase/functions/test/test.js diff --git a/quickstarts/uppercase/functions/package.json b/quickstarts/uppercase/functions/package.json index 856574b..5a81c16 100644 --- a/quickstarts/uppercase/functions/package.json +++ b/quickstarts/uppercase/functions/package.json @@ -3,6 +3,15 @@ "description": "Uppercaser Firebase Functions Quickstart sample", "dependencies": { "firebase-admin": "^4.1.1", - "firebase-functions": "https://storage.googleapis.com/firebase-preview-drop/node/firebase-functions/firebase-functions-preview.latest.tar.gz" + "firebase-functions": "https://storage.googleapis.com/functions-dev-preview/firebase-functions-0.5.1-preview2.tgz" + }, + "devDependencies": { + "chai": "^3.5.0", + "chai-as-promised": "^6.0.0", + "mocha": "^3.2.0", + "sinon": "^1.17.7" + }, + "scripts": { + "test": "./node_modules/.bin/mocha --reporter spec" } } diff --git a/quickstarts/uppercase/functions/test/test.js b/quickstarts/uppercase/functions/test/test.js new file mode 100644 index 0000000..cafdd16 --- /dev/null +++ b/quickstarts/uppercase/functions/test/test.js @@ -0,0 +1,135 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// You can run these unit tests by running "npm test" inside the uppercase/functions directory. + +// Chai is a commonly used library for creating unit test suites. It is easily extended with plugins. +var chai = require("chai"); +var assert = chai.assert; + +// Chai As Promised extends Chai so that a test function can be asynchronous with promises instead of using +// callbacks. It is recommended when testing Cloud Functions for Firebase due to its heavy use of Promises. +var chaiAsPromised = require("chai-as-promised"); +chai.use(chaiAsPromised); + +// Sinon is a library used for mocking or verifying function calls in JavaScript. +var sinon = require('sinon'); + +var functions = require('firebase-functions'); +var admin = require('firebase-admin'); + +describe('Cloud Functions', () => { + var myFunctions, configStub, adminInitStub; + + before(() => { + // Since index.js makes calls to functions.config and admin.initializeApp at the top of the file, + // we need to stub both of these functions before requiring index.js. This is because the functions + // will be executed as a part of the require process. + // Here we stub admin.initializeApp to be a dummy function that doesn't do anything. + adminInitStub = sinon.stub(admin, 'initializeApp'); + // Next we stub functions.config(). Normally config values are loaded from Cloud Runtime Config; + // here we'll just provide some fake values for firebase.databaseURL and firebase.storageBucket + // so that an error is not thrown during admin.initializeApp's parameter check + configStub = sinon.stub(functions, 'config').returns({ + firebase: { + databaseURL: 'https://not-a-project.firebaseio.com', + storageBucket: 'not-a-project.appspot.com', + } + // You can stub any other config values needed by your functions here, for example: + // foo: 'bar' + }); + // Now we can finally require index.js and save the exports inside a namespace called myFunctions. + // This includes our cloud functions, which can now be accessed at myFunctions.makeUppercase and myFunctions.addMessage + myFunctions = require('../index'); + }); + + after(() => { + // Restoring our stubs to the original methods. + configStub.restore(); + adminInitStub.restore(); + }); + + describe('makeUpperCase', () => { + // Test Case: setting messages/11111/original to 'input' should cause 'INPUT' to be written to + // messages/11111/uppercase + it('should upper case input and write it to /uppercase', () => { + const childParam = 'uppercase'; + const setParam = 'INPUT'; + const refStub = sinon.stub(); + const childStub = sinon.stub(); + const setStub = sinon.stub(); + // There are two main ways to create test input: fakes and mocks. Fakes are dumb objects that include + // any data necessary for your test. + const fakeEvent = { + // The DeltaSnapshot constructor is used by the Functions SDK to transform a raw event from your database + // into an object with utility functions such as .val(). + // Its signature is DeltaSnapshot(app: firebase.app.App, adminApp: firebase.app.App, data: any, delta: any, path?: string); + // We can pass null for the first 2 parameters. The data parameter represents the state of the database item before the event, + // while the delta parameter represents the change that occured to cause the event to fire. The last parameter is the database path. + data: new functions.database.DeltaSnapshot(null, null, null, 'input', 'messages/1111/original'), + // To mock a database delete event: + // data: new functions.database.DeltaSnapshot(null, null, 'old_data', null, 'messages/1111/original') + }; + + // Stubs, mocks, & expectations are smarter objects that fake and/or record function calls. + // These are excellent for verifying that functions have been called and to validate the parameters passed to + // those functions. + // The following 4 lines override the behavior of event.data.ref.parent.child('uppercase').set('INPUT') to return true + sinon.stub(fakeEvent.data, 'ref', { get: refStub }); + refStub.returns({ parent: { child: childStub}}); + childStub.withArgs(childParam).returns( { set: setStub }); + setStub.withArgs(setParam).returns(true); + + // All non-HTTPS cloud functions return a promise that resolves with the return value of your code. In this case, we've + // stubbed it to return true if event.data.ref.parent.child(childParam).set(setParam) was called with the parameters we + // expect. We assert that makeUppercase returns a promise that eventually resolves with true. + return assert.eventually.equal(myFunctions.makeUppercase(fakeEvent), true); + }) + }); + + describe('addMessage', () => { + it('should return a 303 redirect', (done) => { + const refParam = '/messages'; + const pushParam = { original: 'input' }; + const refStub = sinon.stub(); + const pushStub = sinon.stub(); + // A fake request object, with req.query.text set to 'input' + const req = { query: {text: 'input'} }; + // A fake response object, with a stubbed redirect function which asserts that it is called with parameters 303, 'new_ref'. + const res = { + redirect: (code, url) => { + assert.equal(code, 303); + assert.equal(url, 'new_ref'); + done(); + } + }; + + // The following 4 lines override the behavior of admin.database().ref('/messages').push({ original: 'input' }) to return a promise that + // resolves with { ref: 'new_ref' }. This mimics the behavior of a push to the database, which returns an object containing a ref + // property representing the URL of the newly pushed item. + databaseStub = sinon.stub(admin, 'database'); + databaseStub.returns( { ref: refStub }); + refStub.withArgs(refParam).returns( { push: pushStub }); + pushStub.withArgs(pushParam).returns( Promise.resolve({ ref: 'new_ref' })); + + // Invoke addMessage with our fake request and response objects. This will cause the assertions in the response object to be evaluated. + myFunctions.addMessage(req, res); + + // Restoring admin.database() to the original method + databaseStub.restore(); + }); + }); +})