I'm writing jest unit tests for my slack bot event handler functions. I'm not testing interaction with the database; I've mocked the function which calls the database, so no actual database calls happen during the tests.
However, because firebase initialised in firebase.ts
which is imported into index.ts
, the test suite fails to run because jest doesn't have the correct environment variables.
What I want to achieve is a way of not instantiating the database at all, when I'm running tests. I've looked at some options for mocking firebase (eg npm packages such ts-mock-firebase
, firebase-mock
, and more manual approach using some other SO answers), but I haven't had any success with them.
So, is there a safe, reasonable way I can stop the database from instantiating during tests? It feels like this should be easy - maybe I'm missing something obvious!
I'm really surprised there doesn't seem to be a kind of community accepted solution to this problem as far as I can tell. Maybe I'm wrong and I've been looking in the wrong places?
Thanks for any wisdom you can offer!
folder structure
- index.ts
database
- firebase.ts
services
- events.ts
- events.test.ts
index.ts
import { loadEnv } from "./config/dotenv";
loadEnv();
import "./database/firebase";
// some app config here...
export app = new App(appOptions);
firebase.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-var-requires */
import * as admin from "firebase-admin";
import firebaseServiceKey from "../config/firebaseServiceKey";
admin.initializeApp({
// current required type appears to be wrong so has to be cast as any
credential: admin.credential.cert(firebaseServiceKey as any),
databaseURL:
process.env.FIREBASE_URL || "https://loopin-c782d.firebaseio.com",
});
export const auth = admin.auth();
export const firestore = admin.firestore();
events.ts
/**
* A user renames the organization name in slack
*/
export const handleTeamRename = async (
userId: string,
token: string
): Promise<void> => {
try {
const team = await getSlackTeam(token, userId);
await updateOrganizationName(team.id, team.name, team.icon.image_230);
} catch (e) {
console.error("team_rename event");
console.error(e);
}
};
events.test.ts
import * as events from "./Events";
import * as dbOrganizations from "../database/Organizations";
import * as dbSlack from "../database/Slack";
import { SlackGeneratedTeam } from "../models/Slack";
describe("events", () => {
const fakeBotToken = "xoxb-123abc";
const fakeOrgId = "fakeOrgId";
const fakeOrgName = "Mega Corp, inc.";
const fakeUrl = "www.example.com";
const fakeSlackTeam = {
id: fakeOrgId,
name: fakeOrgName,
icon: {
image_230: fakeUrl,
},
};
describe("handleTeamRename", () => {
let mockGetSlackTeam: jest.SpyInstance<
Promise<SlackGeneratedTeam>,
[token: string, id: string]
>;
let mockUpdateOrganizationName: jest.SpyInstance<
Promise<void>,
[id: string, displayName: string, photoUrl: string]
>;
beforeEach(() => {
mockGetSlackTeam = jest.spyOn(dbSlack, "getSlackTeam").mockImplementation(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (token: string, id: string) => fakeSlackTeam as SlackGeneratedTeam
);
mockUpdateOrganizationName = jest
.spyOn(dbOrganizations, "updateOrganizationName")
.mockImplementation(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (id: string, displayName: string, photoUrl: string) => {
return;
}
);
});
afterEach(() => {
mockGetSlackTeam.mockRestore();
mockUpdateOrganizationName.mockRestore();
});
it("should make the right calls", async () => {
void (await events.handleTeamRename(fakeOrgId, fakeBotToken));
await expect(mockGetSlackTeam).toHaveBeenCalledTimes(1);
await expect(mockGetSlackTeam).toHaveBeenCalledWith(
fakeBotToken,
fakeOrgId
);
await expect(mockUpdateOrganizationName).toHaveBeenCalledTimes(1);
await expect(mockUpdateOrganizationName).toHaveBeenCalledWith(
fakeOrgId,
fakeOrgName,
fakeUrl
);
});
});
});
error
npm run test events
> api@1.0.0 test
> jest "events"
FAIL src/services/Events.test.ts
● Test suite failed to run
Service account object must contain a string "project_id" property.
6 | admin.initializeApp({
7 | // current required type appears to be wrong so has to be cast as any
> 8 | credential: admin.credential.cert(firebaseServiceKey as any),
| ^
9 | databaseURL:
10 | process.env.FIREBASE_URL || "[my firebase url]",
11 | });
at FirebaseAppError.FirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:43:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:89:28)
at new FirebaseAppError (node_modules/firebase-admin/lib/utils/error.js:124:28)
at new ServiceAccount (node_modules/firebase-admin/lib/credential/credential-internal.js:135:19)
at new ServiceAccountCredential (node_modules/firebase-admin/lib/credential/credential-internal.js:69:15)
at Object.cert (node_modules/firebase-admin/lib/credential/credential.js:113:54)
at Object.<anonymous> (src/database/firebase.ts:8:32)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 3.154 s, estimated 4 s
Ran all test suites matching /events/i.