Im trying to mock this mail function so I dont send mails everytime I test my code. But the mocking is not working. This code gives me the error: mockImplementation is not a function.
It's the add function that calls sendUserInvitationMail(). The mailer module export looks like this:
module.exports = {
sendUserInvitationMail,
};
this is the test code:
require('dotenv').config();
const { startWithCleanDb } = require('../../../utils/test.helpers');
const { add } = require('../invitation.service');
const { ADMIN_LEVELS, TABLES } = require('../../../constants');
const { AuthorizationError } = require('../../../errors');
const knex = require('../../../../db/connection');
const mailer = require('../../../mailer/index');
jest.mock('../../../mailer/index');
beforeEach(() => startWithCleanDb());
mailer.sendUserInvitationMail.mockImplementation(() => console.log('Mocked mail function called'));
mailer.sendUserInvitationMail();
describe('invitation.service', () => {
describe('add', () => {
it('adds an invitation to the db', async () => {
expect.assertions(2);
const result = await add(
{
email: 'tester#test.be',
badgeNumber: '344d33843',
},
{ currentZoneId: 1 },
ADMIN_LEVELS.ADMINISTRATOR,
);
const invitation = (await knex.select('*').from(TABLES.INVITATIONS))[0];
expect(invitation.id).toEqual(result.id);
expect(invitation.email).toEqual(result.email);
});
});
});
In mailer, sendUserInvitationMail is undefined, so it has no property mockImplementation.
Try:
mailer.sendUserInvitationMail = jest.fn().mockImplementation(() => console.log('Mocked mail function called'));
or
mailer.sendUserInvitationMail = jest.fn(() => console.log('Mocked mail function called'));
Related
I followed the Cloud Functions recommendation and created my unit testing in online mode, everything makes sense to me, but I am getting inconsistency when I am debugging. I just trying to reset the firestore state by resetting and seeding the database.
When I run my test suite separately, they work perfectly; if I run all the test suites together appear all the errors, I am pretty sure that this is related to the beforeEach hooks but idk how to fix them.
I have 3 test suites, I will share with you 1 of them and the function responsible of the reset of the firestore.
// eslint-disable-next-line spaced-comment
/// <reference types="jest" />
import { cleanup, getDb, initializeFirebase, makeChange, resetDb, wrap } from './util';
initializeFirebase();
import { streamerIsOffline, streamerIsOnline } from './__fixtures__/onStreamerUpdateSnaps';
import { getScheduler, SchedulerClientWrapper } from './../utils/SchedulerClientWrapper';
import onStreamerUpdate from '../onStreamerUpdate';
const db = getDb();
jest.mock('./../utils/SchedulerClientWrapper');
const mockedGetScheduler = jest.mocked(getScheduler, true);
const wrapped = wrap(onStreamerUpdate);
const schedulerClientWrapper = new SchedulerClientWrapper();
const mockedPause: jest.Mock = jest.fn();
const mockedResume: jest.Mock = jest.fn();
const mockedJobIsEnabled: jest.Mock = jest.fn();
schedulerClientWrapper.pause = mockedPause;
schedulerClientWrapper.resume = mockedResume;
schedulerClientWrapper.jobIsEnabled = mockedJobIsEnabled;
describe('onStreamerUpdate', () => {
beforeEach(
async () => {
await resetDb(); //I am resetting firestore here
mockedGetScheduler.mockClear();
jest.resetAllMocks();
});
it('should resume the scheduledJob when the first streamer becomes online',
async () => {
await updateStreamerStatus('64522496', true); //I am preparing the setup here
const beforeSnap = streamerIsOffline;
const afterSnap = streamerIsOnline;
const change = makeChange(beforeSnap, afterSnap);
mockedGetScheduler.mockReturnValue(schedulerClientWrapper);
mockedJobIsEnabled.mockResolvedValue(false);
await wrapped(change);
expect(mockedJobIsEnabled).toBeCalled();
expect(mockedResume).toBeCalled();
expect(mockedPause).not.toBeCalled();
});
afterAll(() => {
cleanup();
});
});
const updateStreamerStatus = async (streamerId: string, isOnline: boolean) => {
const stremersRef = db.collection('streamers');
const query = stremersRef.where('broadcasterId', '==', streamerId);
const querySnapshot = await query.get();
console.log('streamers', (await stremersRef.get()).docs.map((doc) => doc.data())); //I am debugging here
const streamerDocId = querySnapshot.docs[0].id;
await stremersRef.doc(streamerDocId).update({ isOnline });
};
And you can find the functions that wipe and seed firestore below:
import firebaseFunctionsTest from 'firebase-functions-test';
import { getApp, getApps, initializeApp, cert } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
const projectId = 'latamqchallengetest';
export const { wrap, cleanup, firestore: firestoreTestData, makeChange } = firebaseFunctionsTest({
databaseURL: `https://${projectId}.firebaseio.com`,
storageBucket: `${projectId}.appspot.com`,
projectId: projectId,
}, './../google-credentials/latamqchallengetest-firebase-adminsdk.json');
export const initializeFirebase = () => {
if (getApps().length <= 0) {
initializeApp({
credential: cert('./../google-credentials/latamqchallengetest-firebase-adminsdk.json'),
});
}
return getApp();
};
export const getDb = () => {
initializeFirebase();
const db = getFirestore();
return db;
};
export const wipeDb = async () => {
const db = getDb();
const collections = await db.listCollections();
const deletePromises = collections.map((collectionRef) =>
db.recursiveDelete(collectionRef));
Promise.all([...deletePromises]);
};
export const seed = async () => {
const db = getDb();
await db.collection('streamers').add({
broadcasterId: '64522496',
broadcasterName: 'ITonyl',
color: '1565C0',
description: 'Sr. Truchita',
isOnline: false,
puuid: 'kRQyIDe5TfLhWtn8jXgo_4Zjlfg4rPypXWiPCXrkTMUsiQT3TYVCkVO6Au3QTdd4x-13CbluPA53dg',
summonerId: '9bnJOmLXDjX-sgPjn4ZN1_-f6m4Ojd2OWlNBzrdH0Xk2xw',
});
await db.collection('streamers').add({
broadcasterId: '176444069',
broadcasterName: 'Onokenn',
color: '424242',
description: 'El LVP Player',
isOnline: false,
puuid: 'Gbfj8FyB6OZewfXgAwvLGpkayA6xyevFfEW7UZdrzA6saKVTyntP4HxhBQFd_EIGa_P1xC9eVdy8sQ',
summonerId: 'pPWUsvk_67FuF7ky1waWDM_gho-3NYP4enWTtte6deR3CjxOGoCfoIjjNw',
});
};
export const resetDb = async () => {
await wipeDb();
await seed();
};
Sometimes I find that firestore has 4 records, other times it has 0 records, 2 records and so on .. I don't know why I await the promises before each test.
I expect to keep the same state before each test, can you help me please?
All the problem was the parallel behavior of jest, I just needed to run the tests sequentially with the param --runInBand or maxWorkers=1
When as soon as I send a message, all the messages above it get deleted. In case I fetch all messages again from the database, it works.
So the following is my code body where I am making an amplify request everytime a new message is generated:
const ChatRoomScreen = () => {
const [messages, setMessages] = useState([]);
const [myId, setMyId] = useState(null);
const route = useRoute();
const fetchMessages = async () => {
const messagesData = await API.graphql(
graphqlOperation(
messagesByChatRoom, {
chatRoomID: route.params.id,
sortDirection: "DESC",
}
)
)
//console.log("FETCH MESSAGES")
setMessages(messagesData.data.messagesByChatRoom.items);
}
useEffect(() => {
fetchMessages();
}, [])
useEffect(() => {
const getMyId = async () => {
const userInfo = await Auth.currentAuthenticatedUser();
setMyId(userInfo.attributes.sub);
}
getMyId();
}, [])
useEffect(() => {
const subscription = API.graphql(
graphqlOperation(onCreateMessage)
).subscribe({
next: (data) => {
const newMessage = data.value.data.onCreateMessage;
if (newMessage.chatRoomID !== route.params.id) {
//console.log("Message is in another room!")
return;
}
setMessages([newMessage, ...messages]); //This line creates issues
//If I fetch all messages again from database, it works.
}
});
return () => subscription.unsubscribe();
}, [])
I've been trying to mock
admin.firestore.FieldValue.serverTimestamp().
However once I've
mocked admin.firestore(), all the calls just hit that stub instead.
So the error runnning the test is... Trying to stub property 'serverTimestamp' of undefined
const admin = require("firebase-admin")
const test = require("firebase-functions-test")()
describe("Unit test", () => {
let allFunctions, adminFirestoreStub
beforeAll(() => {
adminFirestoreStub = sinon.stub(admin, "initializeApp")
allFunctions = require("../index")
})
describe("Main", () => {
it("Test", async () => {
const dbStub = sinon.stub()
const collStub = sinon.stub()
sinon.stub(admin, "firestore").get(() => dbStub)
dbStub.returns({
collection: collStub,
})
sinon.stub(admin.firestore.FieldValue, "serverTimestamp").returns(Date())
const wrapped = test.wrap(allFunctions.my_func)
await wrapped(data, context)
})
})
})
Thanks Doug, put a const to serverTimestamp() above the actual function and it worked.
I'm writing unit tests and which to mock the 'exec' method in package 'child_process'.
__mocks__/child_process.js
const child_process = jest.genMockFromModule('child_process');
child_process.exec = jest.fn()
module.exports = child_process;
This is the test file:
const fs = require('fs-extra'),
child_process = require('child_process'),
runCassandraMigration = require('../../lib/runCassandraMigration.js')
const defaultArguments = () => {
return {
migration_script_path: './home',
logger: {
error: function () {}
}
};
}
jest.mock("fs-extra")
jest.mock("child_process")
describe('Running cassandra migration tests', function () {
describe('successful flow', function () {
it('Should pass without any errors ', async function () {
let args = defaultArguments();
let loggerSpy = jest.spyOn(args.logger, 'error')
fs.remove.mockImplementation(() => {Promise.resolve()})
child_process.exec.mockImplementation(() => {Promise.resolve()})
await runCassandraMigration(args.migration_script_path, args.logger)
});
});
When I run the test I get the following error:
child_process.exec.mockImplementation is not a function
The module I test
const fs = require('fs-extra')
const promisify = require('util').promisify
const execAsync = promisify(require('child_process').exec)
module.exports = async (migration_script_path, logger) => {
try {
console.log()
const {stdout, stderr} = await execAsync(`cassandra-migration ${migration_script_path}`)
logger.info({stdout: stdout, stderr: stderr}, 'Finished runing cassandra migration')
await fs.remove(migration_script_path)
} catch (e) {
logger.error(e, 'Failed to run cassandra migration')
throw Error()
}
}
Please advise.
A late... answer?...
Yesterday I got the same error and the problem was that I wasn't calling jest.mock('child_process') in my test file.
Jest documentation says that when mocking Node's core modules calling jest.mock('child_process') is required. I see you do this but for some reason it is not working (maybe Jest is not hoisting it to the top).
Anyways, with Jest version 24.9.0 I don't get the child_process.exec.mockImplementation is not a function error but get some other errors because your test is not well implemented.
To make your test work I:
Added info: function () {}, inside logger
Updated the implementation of exec to child_process.exec.mockImplementation((command, callback) => callback(null, {stdout: 'ok'}))
And also (not necessary for the test to pass) updated the implementation of fs.remove to fs.remove.mockImplementation(() => Promise.resolve())
Like this:
const fs = require('fs-extra'),
child_process = require('child_process'),
runCassandraMigration = require('./stack')
const defaultArguments = () => {
return {
migration_script_path: './home',
logger: {
info: function () {},
error: function () {}
}
};
}
jest.mock("fs-extra")
jest.mock("child_process")
describe('Running cassandra migration tests', function () {
describe('successful flow', function () {
it('Should pass without any errors ', async function () {
let args = defaultArguments();
let loggerSpy = jest.spyOn(args.logger, 'error')
fs.remove.mockImplementation(() => Promise.resolve())
child_process.exec.mockImplementation((command, callback) => callback(null, {stdout: 'ok'}))
await runCassandraMigration(args.migration_script_path, args.logger)
});
});
});
I'm currently trying to test my thunk action (getUserFeatureNames) to see if it calls a success action(getUserFeatureNamesSuccess) using jest. getUserFeatureNames thunk action currently resides in loginActions.js file which is import homeQueries(which i'm trying to mock). So far I'm getting the following error when running my jest test..
TypeError: _homeQueries2.default.getFeatureNames is not a function
How do i mock homeQueries.getFeatureNames?
function createStore(state = {}, expectActions = {}){
const mockStore = configureMockStore([thunk]);
return mockStore(state, expectActions);
}
describe("home_async_tests", () => {
test("getUserFeatureNamesSuccess action is called if request was success", (done) => {
jest.mock('../../../graphQL/homeQueries', () => {
return jest.fn(() => {
{
getFeatureNames: () =>{
return new Promise((resolve, reject) => {
let array = [{iconFile: 'Personalization.png', description: 'Personalization'},{iconFile: 'Home.png', description: 'Home'}];
resolve(array);
});
};
}
});
});
jest.dontMock('../../../app/redux/actions/homeActions');
let homeActions = require('../../../app/redux/actions/homeActions');
const expectedAction = {type: types.userFeatureNamesSuccess, payLoad: {isError: false, data: '' }};
const store = createStore();
store.dispatch(homeActions.getUserFeatureNames({token:"fdis4554" })).then(() => {
const actions = store.getActions();
expect(actions[0].type).toEqual(expectedAction.type);
expect(actions[0].payLoad.isError).toEqual(expectedAction.payLoad.isError);
done();
});
});
I assume that the module just return an object and not a function that returns an object, so your mock should look like this:
jest.mock('../../../graphQL/homeQueries', () = > ({
getFeatureNames: () = > {
return new Promise((resolve, reject) = > {
let array = [{
iconFile: 'Personalization.png',
description: 'Personalization'
}, {
iconFile: 'Home.png',
description: 'Home'
}];
resolve(array);
});
};
}
});