I am testing a react-native component in which imports a dependency (named import and async) which performs some logic and just returns a boolean but i think jest is not waiting for it to finish. which also logs an error.
"Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue."
currently this is the implementation i have tried..
this is the code of the test
// OfflineNotice.test.js
import React from 'react';
import { OfflineNotice } from './OfflineNotice';
jest.mock('../../service/user', () => ({
__esModule: true,
checkIfConnectivityIsValid: () => (
Promise.resolve(true)
),
}));
describe('<OfflineNotice/> test suite', () => {
const mockOnNetworkConnected = jest.fn();
test('it should render <OfflineNotice/> component', () => {
const wrapper = shallow(
<OfflineNotice
onNetworkConnected={mockOnNetworkConnected}
network={{
isConnected: true,
connectionType: 'value',
}}
/>,
);
expect(wrapper).toBeDefined();
});
});
the code of the component that i was testing
// the dependency i need to mock
import { checkIfConnectivityIsValid } from '../../service/user';
// the implementation is as follows
export class OfflineNotice extends PureComponent {
componentWillMount() {
const { network } = this.props;
const { isConnected, connectionType } = network;
this.handleConnectivityChange(isConnected, connectionType);
}
componentDidUpdate() {
const { network } = this.props;
const { isConnected, connectionType } = network;
this.handleConnectivityChange(isConnected, connectionType);
}
handleConnectivityChange = async (isConnected, connectionType) => {
const { onNetworkConnected } = this.props;
// how the service was used only returns boolean
const isValid = await checkIfConnectivityIsValid(connectionType);
let status = null;
let message = null;
if (isConnected && isValid) {
status = 'online';
message = string.NETWORK_MESSAGE.AVAILABLE;
this.fadeOut();
onNetworkConnected();
} else if (isConnected) {
status = 'invalid';
message = string.NETWORK_MESSAGE.INVALID;
this.containerOpacity.setValue(1);
} else {
status = 'offline';
message = string.NETWORK_MESSAGE.NO_INTERNET;
this.containerOpacity.setValue(1);
}
this.setState({ status, message });
};
then running the test is able to render the component. though on code coverage the code stops on the "const isValid = await checkIfConnectivityIsValid(connectionType);" part in which it says that the statement onward is not covered.
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
After much trial/error, searching here on SO, & flexing my Google Fu, throwing in the towel & asking for help.
TL/DR -
Trying to correctly mock node module, change internal method return types, and spy on ctor's & method calls within the node module.
My specific scenario is to test the Microsoft Azure Storage blob SDK #azure/storage-blob, but the questions aren't specific to this package. It's just a good example as 4 LOC's capture achieve a task (upload a file to a storage container) as 2-3 of those LOC's cover 4 scenarios. Here's my code that I want to test, with comments on WHAT exactly I want to test:
export async function saveImage(account: string, container: string, imageBuffer: Buffer, imageName: string): Promise<void> {
try {
// init storage client
// (1) want to spy on args passed into ctor
const blobServiceClient: BlobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, new DefaultAzureCredential());
// init container
// (2) same as #1
const containerClient: ContainerClient = await blobServiceClient.getContainerClient(container);
// init block blob client
// (3) same as #1 & #2
const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(imageName);
// save file
// (4) same as #1,2, & 3
// (5) manipulate returned value
// (6) throw cause method to internally throw error
await blockBlobClient.upload(imageBuffer, imageBuffer.length, { blobHTTPHeaders: { blobContentType: 'image/png' } });
return Promise.resolve();
} catch (err: Error) {
return Promise.reject(err);
}
}
I've setup a manual mock for the module in the ./__mocks/#azure/storage-blob.ts as follows:
const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob');
/**
* Utility method throw exception in the `BlockBlobClient.upload()` method.
*/
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
(MockStorageBlob as any).BlobServiceClient = jest.fn().mockImplementation(() => {
return {
getContainerClient: jest.fn().mockReturnValue({
getBlockBlobClient: jest.fn().mockReturnValue({
upload: jest.fn().mockImplementation(() => {
throw new Error('synthetic error');
})
})
})
}
});
}
module.exports = MockStorageBlob;
In my test, I can successfully test for #6 above like this:
import {
BlockBlobClient,
BlockBlobUploadResponse
} from '#azure/storage-blob';
import { saveImageToCDN as functionUnderTest } from './saveImageToCDN';
// mock Azure Storage blob NPM package
jest.mock('#azure/storage-blob');
describe('check expected with failure', () => {
beforeEach((done) => {
// reset number of times things have been called
jest.clearAllMocks();
done();
});
test(`it calls 'trackException()' when upload throws exception`, async (done) => {
expect.assertions(1);
// setup test
require('#azure/storage-blob').__setBlockBlobUpload_toFail();
// run SUT
const imageBuffer = Buffer.from('test string');
functionUnderTest(imageBuffer, 'imageName.png')
.then(() => {
expect(new Error('should not reach this')).toBeUndefined();
})
.catch((err: Error) => {
expect(err).toBeDefined();
})
.finally(() => {
done();
});
});
});
... but I can't figure out the correct syntax to spy on the upload() method (#4), or any of the other things I'm trying to test for (#1-5). If it matters, using Jest v26 on Node v14.
Could the __setBlockBlobUpload_toFail function return references to the mock functions ?
That would give something like this :
const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob');
/**
* Utility method throw exception in the `BlockBlobClient.upload()` method.
*/
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
const upload = jest.fn().mockImplementation(() => {
throw new Error('synthetic error');
});
const getBlockBlobClient = jest.fn().mockReturnValue({ upload });
const getContainerClient = jest.fn().mockReturnValue({ getBlockBlobClient });
const BlobServiceClient = jest.fn().mockImplementation(() => {
return {
getContainerClient
}
});
(MockStorageBlob as any).BlobServiceClient = BlobServiceClient;
return {
upload,
getBlockBlobClient,
getContainerClient,
BlobServiceClient
};
}
module.exports = MockStorageBlob;
And in your test you would retrieve them like :
// setup test
const mockFns = require('#azure/storage-blob').__setBlockBlobUpload_toFail();
// run SUT
const imageBuffer = Buffer.from('test string');
functionUnderTest(imageBuffer, 'imageName.png')
.then(() => {
expect(new Error('should not reach this')).toBeUndefined();
})
.catch((err: Error) => {
expect(mockFns.getBlockBlobClient.mock.calls[0][0]).toBe('imageName.png')
expect(err).toBeDefined();
})
.finally(() => {
done();
});
Context
I am trying to write a jest test for an authentication middleware for a resolver function. I am attempting to mock an implementation so that the next function is called so that the test passes.
Error
The error I receive is "next is not a function". I can verify that the mocked function is called through expect(isAuth).toHaveBeenCalledTimes(1);, but there is clearly an issue with my mocked implementation. Any help is much appreciated.
Code
//isAuth Middleware
import { MiddlewareFn } from "type-graphql";
import { Context } from "../utils/interfaces/context";
export const isAuth: MiddlewareFn<Context> = ({ context }, next) => {
const loggedInUserId = context.req.session.id;
if (!loggedInUserId) {
throw new Error("Not authenticated!");
}
return next();
};
//transaction.test.ts
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
test("should create a txn successfully", async () => {
//ARRANGE
const user = await createUser(orm);
const txn = createTxnOptions();
const txnToBeCreated = { ...txn, userId: user.id };
//ACT
const response = await testClientMutate(
TXN_QUERIES_AND_MUTATIONS.CREATE_TXN,
{
variables: txnToBeCreated,
}
);
//expect(isAuth).toHaveBeenCalledTimes(1); passes so it's getting called
console.log(response);
const newlyCreatedTxn: Transaction = (response.data as any)
?.createTransaction;
//ASSERT
const dbTxn = await em.findOne(Transaction, {
id: newlyCreatedTxn.id,
});
expect(newlyCreatedTxn.id).toBe(dbTxn?.id);
});
//transaction.resolver.ts
import { Transaction } from "../entities/Transaction";
import {
Arg,
Ctx,
Mutation,
Query,
Resolver,
UseMiddleware,
} from "type-graphql";
import { Context } from "../utils/interfaces/context";
import { isAuth } from "../middleware/isAuth";
#Mutation(() => Transaction)
#UseMiddleware(isAuth)
async createTransaction(
#Arg("title") title: string,
#Arg("userId") userId: string,
#Ctx() { em }: Context
): Promise<Transaction> {
const transaction = em.create(Transaction, {
title,
user: userId,
});
await em.persistAndFlush(transaction);
return transaction;
}
Replace
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
With
jest.mock("../middleware/isAuth", () => {
return {
isAuth: (_, next) => next()
};
});
I'm using NodeJS and a MongoDB. I have this simple function for returning a generic property of a document ...
import mongoose, { Document, Schema } from "mongoose";
export interface IMyObject extends Document {
...
}
...
export async function getProperty(
req: CustomRequest<MyDto>,
res: Response,
next: NextFunction
): Promise<void> {
const {
params: { propertyName, code },
} = req;
try {
const my_obj = await MyObject.findOne({ code });
const propertyValue = my_obj ? my_obj.get(propertyName) : null;
if (propertyValue) {
res.status(200).json(propertyValue);
...
I'm struggling to figure out how to test this function. In particular, how do I mock an instance of my object that's compatible with the "get" method? I tried this
it("Should return the proper result", async () => {
const myObject = {
name: "jon",
};
MyObject.findOne = jest.fn().mockResolvedValue(myObject.name);
const resp = await superTestApp.get(
"/getProperty/name/7777"
);
expect(resp.status).toBe(StatusCodes.OK);
expect(resp.body).toEqual("happy");
but this fails with
TypeError: my_object.get is not a function
You would need to spy your object and its methods. Something like:
import MyObject from '..';
const mockedData = {
get: (v) => v
};
let objectSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
objectSpy = jest.spyOn(MyObject, 'findOne');
objectSpy.mockReturnValue(mockedData);
});
// clear the mock the method after all tests execution
afterAll(() => {
objectSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', () => {
const response = MyObject.findOne();
expect(response.get('whatever')).toEqual(mockedData.get('whatever'));
});
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'));