你如何单独测试用快递包裹的firebase功能?

时间:2018-03-18 19:33:40

标签: firebase google-cloud-functions

使用firebase功能,你可以利用express来实现很好的功能,比如中间件等。我使用this example来获取如何编写https firebase功能的灵感,由express提供支持。

但是,我的问题是the official firebase documentation on how to do unit testing,不包含https-express示例。

所以我的问题是如何单元测试以下函数(打字稿)?:

struct

7 个答案:

答案 0 :(得分:1)

您可以使用supertest与Firebase中的guide配对。以下是测试应用的一个非常基本的示例,但是,通过集成mocha,您可以使其更复杂/更好。

import * as admin from 'firebase-admin'
import * as testFn from 'firebase-functions-test'
import * as sinon from 'sinon'
import * as request from 'supertest'
const test = testFn()
import * as myFunctions from './get-tested' // relative path to functions code
const adminInitStub = sinon.stub(admin, 'initializeApp')

request(myFunctions.app)
  .get('/helloWorld')
  .expect('hello world')
  .expect(200)
  .end((err, res) => {
    if (err) {
      throw err
    }
  })

答案 1 :(得分:0)

mock-express之类的东西会对你有用吗?它应该允许您测试路径而不会实际强制您创建快速服务器。

https://www.npmjs.com/package/mock-express

答案 2 :(得分:0)

对于本地和无网络单元测试,您可以将app.get("helloWorld", ...)回调重构为一个单独的函数,并使用模拟对象调用它。

一般方法是这样的:

main.js:

// in the Firebase code:
export function helloWorld(req, res) { res.send(200); }
app.get('helloWorld', helloWorld);

main.spec.js:使用jasmine&兴农

// in the test:
import { helloWorld } from './main.js';
import sinon from 'sinon';
const reqMock = {};
const resMock = { send: sinon.spy() }; 

it('always responds with 200', (done) => {
    helloWorld(reqMock, resMock);
    expect(resMock.send.callCount).toBe(1);
    expect(resMock.send).toHaveBeenCalledWith(200);
});

答案 3 :(得分:0)

测试是建立信心或信任。

我将从单元测试FireBase中的函数开始。如果没有定义更多要求,我会遵循文档。通过单元测试后,您可以考虑在Express级别进行哪种类型的测试。请记住,您已经测试过该功能,在Express级别测试的唯一方法是映射是否正确。在该级别进行的一些测试应该足以确保映射不会变得过时#34;由于一些变化。

如果你想在不涉及数据库的情况下测试Express级别以上,那么你会看一个模拟框架,就像你的数据库一样。

希望这有助于您考虑所需的测试。

答案 4 :(得分:0)

我已经使用firebase-functions-testnode-mocks-http来完成此工作。

我有这个实用程序类FunctionCaller.js:

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute statement

at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:937)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:471)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:396)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:197)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:130)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:471)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:213)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:154)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:65)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:890)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:304)
at com.sun.proxy.$Proxy141.merge(Unknown Source)
at easycare.dao.AbstractRepository.save(AbstractRepository.java:50)
at easycare.alc.dao.HstDataRepository.save(HstDataRepository.java:43)
at easycare.alc.dao.HstDataRepository$$FastClassBySpringCGLIB$$54ffc850.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at easycare.alc.dao.HstDataRepository$$EnhancerBySpringCGLIB$$9394230f.save(<generated>)
at easycare.alc.dao.remover.HstDataRemoverTest.setupHstData(HstDataRemoverTest.java:43)
at easycare.alc.dao.remover.HstDataRemoverTest.access$000(HstDataRemoverTest.java:22)
at easycare.alc.dao.remover.HstDataRemoverTest$1.run(HstDataRemoverTest.java:37)
at easycare.dao.TransactionWrapper.runWithinTransaction(TransactionWrapper.java:20)
at easycare.dao.TransactionWrapper$$FastClassBySpringCGLIB$$be34760c.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at easycare.dao.TransactionWrapper$$EnhancerBySpringCGLIB$$b13ea1c3.runWithinTransaction(<generated>)
at easycare.dao.NonTransactionalRepositoryTest.runWithinTransaction(NonTransactionalRepositoryTest.java:206)
at easycare.alc.dao.remover.HstDataRemoverTest.setUpAfterDbClean(HstDataRemoverTest.java:34)
at easycare.dao.NonTransactionalRepositoryTest.setUp(NonTransactionalRepositoryTest.java:192)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.hibernate.exception.SQLGrammarException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.execute(ResultSetReturnImpl.java:142)
at org.hibernate.id.IdentityGenerator$InsertSelectDelegate.executeAndExtract(IdentityGenerator.java:87)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3083)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3676)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:375)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:292)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:200)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:131)
at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:236)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:216)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:154)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:926)
... 82 more
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Invalid object name 'hst_events'.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:215)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1635)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:426)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:372)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:5846)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:1719)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:184)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:159)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.execute(SQLServerPreparedStatement.java:354)
at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.execute(ResultSetReturnImpl.java:128)
... 99 

并且我的应用程序已安装为应用程序:

'use strict';

var httpMocks       = require('node-mocks-http');
var eventEmitter    = require('events').EventEmitter;

const FunctionCaller = class {

  constructor(aYourFunctionsIndex) {
    this.functions_index = aYourFunctionsIndex;
  }

  async postFunction(aFunctionName,aBody,aHeaders,aCookies) {

    let url = (aFunctionName[0]=='/') ? aFunctionName : `/${aFunctionName}`;
    let options = {
      method: 'POST',
      url: url,
      body: aBody
    };
    if (aHeaders)
      options.headers = aHeaders;

    if (aCookies) {
      options.cookies = {};
      for (let k in aCookies) {
        let v = aCookies[k];
        if (typeof(v)=='string') {
          options.cookies[k] = {value: v};
        } else if (typeof(v)=='object') {
          options.cookies[k] = v;
        }
      }
    }

    var request = httpMocks.createRequest(options);
    var response = httpMocks.createResponse({eventEmitter: eventEmitter});


    var me = this;
    await new Promise(function(resolve){
      response.on('end', resolve);
      if (me.functions_index[aFunctionName])
        me.functions_index[aFunctionName](request, response);
      else
        me.functions_index.app(request, response);
    });
    return response;
  }

  async postObject(aFunctionName,aBody,aHeaders,aCookies) {
    let response = await this.postFunction(aFunctionName,aBody,aHeaders,aCookies);
    return JSON.parse(response._getData());
  }

  async getFunction(aFunctionName,aParams,aHeaders,aCookies) {
    let url = (aFunctionName[0]=='/') ? aFunctionName : `/${aFunctionName}`;
    let options = {
      method: 'GET',
      url: url,
      query: aParams   // guessing here
    };
    if (aHeaders)
      options.headers = aHeaders;

    if (aCookies) {
      options.cookies = {};
      for (let k in aCookies) {
        let v = aCookies[k];
        if (typeof(v)=='string') {
          options.cookies[k] = {value: v};
        } else if (typeof(v)=='object') {
          options.cookies[k] = v;
        }
      }
    }

    var request = httpMocks.createRequest(options);
    var response = httpMocks.createResponse({eventEmitter: eventEmitter});

    var me = this;
    await new Promise(function(resolve){
      response.on('end', resolve);
      if (me.functions_index[aFunctionName])
        me.functions_index[aFunctionName](request, response);
      else
        me.functions_index.app(request, response);
    });
    return response;
  }

  async getObject(aFunctionName,aParams,aHeaders,aCookies) {
    let response = await this.getFunction(aFunctionName,aParams,aHeaders,aCookies);
    return JSON.parse(response._getData());
  }

};

module.exports = FunctionCaller;

并且我的firebase.json包含:

exports.app = functions.https.onRequest(expressApp);

在顶部的测试文件中,我这样做:

"rewrites": [
  :
  :
  :
  {
    "source": "/path/to/function", "function": "app"
  }
]

然后在测试中我要做:

const FunctionCaller = require('../FunctionCaller');
let fire_functions = require('../index');
const fnCaller = new FunctionCaller(fire_functions);

它以anObject作为request.body调用我的函数并返回响应对象。

我正在Firebase上使用节点8进行异步/等待等。

答案 5 :(得分:0)

这与Jest一起使用

import supertest from 'supertest'
import test from 'firebase-functions-test'
import sinon from 'sinon'
import admin from 'firebase-admin'

let undertest, adminInitStub, request
const functionsTest = test()

beforeAll(() => {
  adminInitStub = sinon.stub(admin, 'initializeApp')
  undertest = require('../index')
  // inject with the exports.app methode from the index.js
  request = supertest(undertest.app)
})

afterAll(() => {
  adminInitStub.restore()
  functionsTest.cleanup()
})

it('get app', async () => {
  let actual = await request.get('/')
  let { ok, status, body } = actual
  expect(ok).toBe(true)
  expect(status).toBeGreaterThanOrEqual(200)
  expect(body).toBeDefined()
})

答案 6 :(得分:-2)

您可以使用postman申请进行单元测试。 输入以下带有项目名称的URL

https://us-central1-your-project.cloudfunctions.net/hello

app.get('/hello/',(req, res) => {
   res.send('hello world');
   return 'success';
});