Cannot stub admin functions with firebase cloud functions - javascript

When i try to call the wrapped function on my data I get the following error message:
Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.
My code looks like the following:
const {expect} = require("chai")
const sinon = require('sinon');
const admin = require('firebase-admin');
const test = require('firebase-functions-test')()
describe('Unit tests', () => {
let myFunctions, adminInitStub;
before(() => {
adminInitStub = sinon.stub(admin, 'initializeApp');
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
test.cleanup()
})
describe("addSchoolVerified", () => {
it('should create school with value-pair "name":"placeholder"', async () => {
const wrapped = test.wrap(myFunctions.addSchoolVerified)
const data = {
name: 'placeholder'
}
// Call the wrapped function with data and context
const result = await wrapped(data)
// Check that the result looks like we expected.
expect(result).to.not.be.undefined
})
})
})
so far I tried using different ways of calling the stub like calling it outside of the before() but that too does result in the above error
index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
exports.addSchoolVerified = functions.https.onCall(async (data, context) => {
/*if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}*/
const schoolName = data.name;
const writeResult = await admin.firestore().collection('Schule').add({name: schoolName})
return {result: `School with id: ${writeResult.id} successfully added.`}
});

Related

How do I implement a unit test for a function that invokes two other functions

Inside this module, I want to test the getAppts function, which invokes two function. What is the correct way of evaluating the code that getAppts encompasses? Do I have to run db.getDatabase() and fetchAppts() as stubs inside the unit test function? My unit test implementation is, at best, incomplete, but it could be totally wrong.
'use strict'
const db = require('../db/db')
const log = require('../utils/logging')(__filename)
const { fetchAppts, fetchApptDetails } = require('../db/dao/appointment-dao')
async function getAppts (queryParams) {
log.debug('getAppts(): %j', queryParams)
const knex = db.getDatabase()
const appointments = await fetchAppts(queryParams, knex)
return appointments
}
async function getApptDetails (demoApptId) {
log.debug('getApptDetails(): %j', demoApptId)
const knex = db.getDatabase()
const apptDetails = await fetchApptDetails(demoApptId, knex)
return apptDetails
}
module.exports = {
getAppts,
getApptDetails
}
unit test setup:
'use strict'
const sinon = require('sinon')
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const dirtyChai = require('dirty-chai')
const sinonChai = require('sinon-chai')
const { expect } = require('chai')
const proxyquire = require('proxyquire')
const apptsResponse = require('../../data/get-appts-response.json')
chai.use(dirtyChai)
chai.use(sinonChai)
chai.use(chaiAsPromised)
chai.should()
describe.only('appointment-service.js', () => {
let apptService
let apptDaoStub
let dbStub
beforeEach(() => {
dbStub = {
getDatabase: sinon.stub()
}
apptDaoStub = {
fetchAppts: sinon.stub()
}
apptService = proxyquire('../../../services/appointment-service', {
'../db/db': dbStub,
'../db/dao/appointment-dao': apptDaoStub
})
})
afterEach(() => sinon.restore())
describe('getAppts', () => {
it.only('should get appointments', async () => {
const appts = await apptService.getAppts({})
dbStub.getDatabase.should.have.been.calledTwice
})
})
})
You can use mock functions, you will need to import the module:
it('should get appointments', async () => {
const mockFn = jest.spyOn(db, 'getDatabase');
const appts = await apptService.getAppts({})
expect(mockFn).toBeCalledTimes(1); // depends on how many times fn is called
});
You can learn more about mocking here:
https://www.chakshunyu.com/blog/how-to-mock-only-one-function-from-a-module-in-jest/

Is it possible to mock API calls with Jest in NodeJS without jest.mock('module')?

My NodeJS application has to do some API requests, so I'm mocking their return as my tests are just for my application's business logic. However, there's two things that I quite didn't understand.
I'm using jest's mockImplementation method to change the return of my service, but I can't make it work without calling jest.mock with the service beforehand.
Also, if I try to set automock: true in my jest.config.js, it returns me an error:|
TypeError: Cannot set property 'gracefulify' of undefined
Here's my test.js code in where I'm testing a function that calls automation.js, which has my application logic and make the calls for my services:
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
jest.mock('../services/bling-product.service');
jest.mock('../services/shopify-product.service');
describe('Automation test', () => {
beforeEach(() => {
const blingMockedReturn = jest.fn(() => {
return mockedBlingProduct;
});
const shopifyMockedReturn = jest.fn(() => {
return mockedShopifyCreatedProduct;
});
BlingProductService.mockImplementation(() => {
return {
list: blingMockedReturn
};
});
ShopifyProductService.mockImplementation(() => {
return {
create: shopifyMockedReturn
};
});
});
it('should return status SUCCESS', async () => {
const result = await
.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});
And here's the code of one of my services, keep in mind that the logic behind the API calls is abstracted from the service. In the mockImplementation I'm trying to overwrite the list and create functions inside them:
class BlingPriceService {
async list(query = {}) {
const httpMethod = 'GET';
const resource = 'produtos/page={pageNumber}/json';
const options = {
queryString: query,
urlParams: {
pageNumber: 1,
}
};
return blingComponent.request(httpMethod, resource, options);
}
}
module.exports = BlingPriceService;
const automation = require('../automations/fake.automation');
// MOCKS
const mockedBlingProduct = require('../mocks/bling-product.mocks.json');
const mockedShopifyCreatedProduct = require('../mocks/shopify-created-product.mocks.json');
// SERVICES
const BlingProductService = require('../services/bling-product.service');
const ShopifyProductService = require('../services/shopify-product.service');
describe('Automation test', () => {
beforeAll(() => {
jest.spyOn(BlingProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedBlingProduct));
jest.spyOn(ShopifyProductService.prototype, 'list').mockImplementation(() => Promise.resolve(mockedShopifyCreatedProduct));
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should return status SUCCESS', async () => {
const result = await automation.run();
expect(result).toEqual({ status: 'SUCCESS' });
});
});

Jest mock not allowing inner function to be resolved in individual test

I'm testing a cloud function named myCloudFn in my functions/send.js module. My tests are in functions/test/send.test.js:
// send.js
const { getCompareDate } = require('../utils.js');
async function myCloudFn(myTestDate) {
const compareDate = await getCompareDate(argToTest);
const isOlder = myTestDate < compareDate;
return isOlder ? 'older' : 'newer';
}
module.exports = { myCloudFn };
// send.test.js
const send = require('../send.js');
jest.mock('../utils', () => ({
getCompareDate: jest.fn(() => new Date('2020-01-31')) // default
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02')),
}));
describe('send.js', () => {
it('returns date comparison from myCloudFn()', async () => {
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
});
The test functions correctly and passes as expected when I mock getCompareDate in this way, but for flexibility, I would rather provide custom input values for getCompareDate inside my tests and not 'globally'. Here's what I've tried:
const mockGetCompareDate = jest.fn();
jest.mock('../utils', () => ({
getCompareDate: mockGetCompareDate,
}));
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
const myTestDate = '2020-03-03';
const returnValues = ['older', 'newer'];
const responsePromises = returnValues.map(() => send.myCloudFn(myTestDate));
const responses = await Promise.all(responsePromises);
expect(responses[0]).toBe(returnValues[0]);
expect(responses[1]).toBe(returnValues[1]);
});
This method, however, is not working and throws an error:
ReferenceError: Cannot access 'mockGetCompareDate' before initialization
I've used this method with other tests as noted in the solution in this question, but I am not seeing similar results here. What am I missing?
Jest is hoisting the mocked function to the top of the module, and hence throws this error. The mock should instead be used right before you run the test. Further reading.
Try this:
const { getCompareDate } = require('../utils.js');
const mockGetCompareDate = jest.fn(() => new Date('2020-01-31'));
jest.mock('../utils.js', () => ({
__esModule: true,
getCompareDate: jest.fn(),
default: jest.fn()
}));
beforeAll(() => {
getCompareDate.mockImplementation(mockGetCompareDate);
});
To provide custom values do as you did before, when initialising the mock function. Source
Like this:
const mockGetCompareDate = jest.fn()
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));
Or do as you did before inside the test. Source
Like this:
it('returns date comparison from myCloudFn()', async () => {
mockGetCompareDate
.mockResolvedValueOnce(new Date('2020-04-04'))
.mockResolvedValueOnce(new Date('2020-02-02'));

How to mock a callback function from bookshelf js using sinnon

I would like to mock this piece of code that is using bookshelf js (with knex) with sinon.
const campaigns = await models.Campaign.forge()
.query((qb) => {
qb.where("account_id", accountId);
qb.andWhere("status", models.Campaign.STATUS.ACTIVE);
qb.andWhere(
"audience_create_trigger",
models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER
);
})
.fetchAll();
How could I mock the inner queries inside the .query function.
I am a bit lost
Thank you very much!
I finally solved the problem using Sinon. This code is covering almost all the behavior
const assert = require("chai").assert
const sinon = require("sinon")
const models = require("../../../../models")
const query = {
query(func) {}
}
const qb = {
where(arg1, arg2) {},
andWhere(arg1, arg2) {}
}
const fetchAll = { async fetchAll() {} }
const forgeStub = sinon.stub(models.Campaign, "forge").returns(query)
const qbWhereStub = sinon
.stub(qb, "where")
.withArgs("account_id", accountId)
.returns(null)
const qbAndWhereStub = sinon.stub(qb, "andWhere").returns(null)
const queryStub = sandbox
.stub(query, "query")
.callsArgWith(0, qb)
.returns(fetchAll)
const fetchAllStub = sandbox.stub(fetchAll, "fetchAll").returns(campaigs)
//Calling the method
//Verify
assert.equal(qbWhereStub.callCount, 1)
assert.equal(qbAndWhereStub.callCount, 2)
assert.equal(forgeStub.callCount, 1)
assert.equal(queryStub.callCount, 1)
assert.equal(fetchAllStub.callCount, 1)
assert.isTrue(qbWhereStub.getCall(0).calledWithExactly("account_id", accountId))
assert.isTrue(qbAndWhereStub.getCall(0).calledWithExactly("status", models.Campaign.STATUS.ACTIVE))
assert.isTrue(
qbAndWhereStub
.getCall(1)
.calledWithExactly("audience_create_trigger", models.Campaign.AUDIENCE_CREATE_TRIGGER.ON_ENTER)
)
Try to use knex-mock-client to setup a test friendly knex instance.
The test will look much simpler.
import knex from "knex";
import { MockClient, getTracker } from "knex-mock-client";
describe("test", () => {
let db;
let tracker;
beforeAll(() => {
db = knex({ client: MockClient });
tracker = getTracker();
});
afterEach(() => tracker.reset());
it("should do something", () => {
tracker.on
.select(
(query) =>
query.sql.includes("table_name") && query.bindings.includes(accountId)
)
.responseOnce([]);
// execute query using db;
expect(tracker.history.select).toHaveLength(1);
});
});

Unable to unit test firebase function in Sinon

I am trying to unit test a JavaScript promise, that contains a firebase query, using mocha, chai, sinon. I am trying to mock the database using sinon rather than actually making a request to the database. However, I am unable to implement it correctly.
Here is my promise in the file '/services/pr_services:
exports.getUserInfo = (userId) => {
return new Promise((resolve, reject) => {
const userProfile = {};
const userProfileRef = database.ref('profiles').child(userId);
userProfileRef.once('value', (snap) => {
if (snap.exists()) {
const userProfileData = snap.val();
resolve(userProfile);
} else {
reject();
}
});
});
};
The variable database contains the database configuration like credentials, database url, etc
Here is my code for the test case:
const chai = require('chai');
const sinon = require('sinon');
const admin = require('firebase-admin');
const database = require('../database');
const services = require('../services/pr_services');
const should = chai.should();
describe('Database functions', () => {
let adminInitStub;
before(() => {
adminInitStub = sinon.stub(admin, 'initializeApp');
});
describe('get profile info', () => {
it('should return a non empty object', (done) => {
beforeEach(() => {
services.getUserInfo = sinon.stub();
});
afterEach(() => {
services.getUserInfo.reset();
});
const userId = 'jim123';
const snap = {
name: 'Jim Dani',
address: 'Porto'
};
const userProfileRef = database.ref('profiles').child(userId);
userProfileRef.once('value').returns(Promise.resolve(snap));
services.getUserInfo
.then(info => {
info.should.be.a('object');
info.should.equal(snap);
done();
})
.catch(err => {
should.not.exist(err);
done();
});
});
});
after(() => {
adminInitStub.restore();
test.cleanup();
});
});
Can anyone point out where I am going wrong and kindly point me in right direction.
Thanks.

Categories