How to write test jest of async/await is toHaveBeenCalled? - node.js

`Trying to run a test for the following code using Jest.I mocked the function and checked if it runs with the desired intent. I think the error is in the promise but I can't find a solution. Can you help me?
Why redis.set not call
i get error
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
mailparser.js
mail parser will call with mgs(‘body’,(stream)=>{parser})
const parser = async (stream) => {
try {
const mail = await simpleParser(stream)
const isAvailable = await redis.set(
[`mail_message_id_${mail.messageId}`],
[`${mail.messageId}`],
{
flag: 'NX',
expiry: 60 * 30,
}
)
if (!isAvailable) {
return console.log(`${mail.messageId} is already processed.`)
}
} catch (err) {
console.error(err, 'failed to parser mail')
}
}
Here my unit Ttesting
const stream = 'stream'
const mockParser = {
simpleParser: jest.fn(),
}
const mockRedis = {
set: jest.fn(),
}
beforeAll(() => {
jest.mock('./redis', () => {
return {
set: mockRedis.set,
}
})
jest.mock('mailparser', () => {
return {
simpleParser: mockParser.simpleParser,
}
})
jest.fn().mockImplementation(() => mockMgs)
})
beforeEach(() => {
mockMgs.on.mockImplementationOnce((event, fn) => {
fn(stream)
})
})
afterEach(async () => {
await jest.clearAllMocks()
})
describe('mailparser', () => {
test('should call redis.set with params correctly', async () => {
const mailparser = require('./mailparser')
await mockParser.simpleParser.mockImplementation(() =>
Promise.resolve(mail) //mail is object data
)
mockRedis.set.mockImplementationOnce(() => Promise.resolve('OK'))
mailparser(mockMgs, 1)
expect(mockRedis.set).toHaveBeenCalled()
})

Related

How to test throwing error from module root (not method)

We have module within there is some initial logic which test that some value was configured, if not, it throws error.. then also the module provides methods. I want to describe this in specification (test), using Jest framework and test the feature. Here is simplified reproduced example:
// dependency.service.ts
export const something = {
method() {
return "methodValue";
}
};
export default function somethingElse() {
return "somethingElseValue";
}
// index.ts
import somethingElse, { something } from "./dependency.service";
const value1 = somethingElse();
const value2 = something.method();
console.log("ROOT somethingElse", value1);
console.log("ROOT something.method", value2);
// initialisation of module fails
if(value1 === 'throw_error' || value2 === 'throw_error') {
throw 'Some error';
}
export function smElse() {
const value = somethingElse();
console.log("somethingElse", value);
return value;
}
export function smMethod() {
const value = something.method();
console.log("somethingElse", value);
return value;
}
// index.spec.ts
import { smElse, smMethod } from './index';
jest.mock('./dependency.service', () => ({
__esModule: true,
default: jest.fn(() => 'MOCKED_somethingElseValue'),
something: {
method: jest.fn(() => 'MOCKED_methodValue'),
},
}));
describe('index', () => {
// some tests for happy paths
it('smElse returns mocked value', () => {
expect(smElse()).toMatchInlineSnapshot(`"MOCKED_somethingElseValue"`);
});
it('smMethod returns mocked value', () => {
expect(smMethod()).toMatchInlineSnapshot(`"MOCKED_methodValue"`);
});
it('smElse returns per test mocked value', () => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('ANOTHER_MOCKED_somethingElseValue');
expect(smElse()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_somethingElseValue"`);
});
it('smMethod returns per test mocked value', () => {
const something = require('./dependency.service').something;
something.method.mockReturnValueOnce('ANOTHER_MOCKED_methodValue');
expect(smMethod()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_methodValue"`);
});
// this is testing the throwing error in module root
it('throws error when somethingElse returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
it('throws error when something.method returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingMethodMocked = require('./dependency.service').something.method;
somethingMethodMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
});
"Try catch in isolation" solution does not work with async code as isolateModules method does not support async functions yet, reference: https://github.com/facebook/jest/issues/10428. I need alternative solution which would support async code.
Whole reproduced example repo here: https://github.com/luckylooke/jestTestModuleRootThrow/tree/main
EDIT:
I found out that some asynchronicity is supported by isolateModules, at least my use case, once I used expect.assertions(1) following test works as expected:
it('throws error when token data are not valid', async () => {
expect.assertions(1);
jest.isolateModules(async () => {
require('crypto-package').someMethod.mockReturnValueOnce(Promise.resolve({
decoderMethod: () => Promise.resolve('{ broken data }'),
}));
const { getTokenData } = require("./decoder.service");
await expect(getTokenData('34534xxxxxxxxxxxxxxx12628')).rejects.toMatchInlineSnapshot(`"Invalid token data"`);
});
});

How to mock 'readline.createInterface' in jest tests

I need to test 'readline.createInterface'.
Below is the code that I need to test:
private createReadStreamSafe(filePath: string): Promise<fs.ReadStream> {
return new Promise((resolve, reject) => {
const fileStream = fs.createReadStream(filePath)
console.log('file Stream')
fileStream
.on('error', () => {
reject('create read stream error')
})
.on('open', () => {
resolve(fileStream)
})
})
}
async start() {
const fileStream = await this.createReadStreamSafe(this.filePath)
const rl = readline.createInterface({
input: fileStream,
output: process.stdout,
terminal: false
})
for await (const line of rl) {
...
}
}
I tried following code:
it('should work', async () => {
const mockedReadStream = new Readable()
jest.spyOn(fs, 'createReadStream').mockReturnValue(mockedReadStream as any)
jest.spyOn(readline, 'createInterface').mockImplementation(() => {
const lines = ['text', 'text2', 'text3']
return {
[Symbol.asyncIterator]() {
return {
i: 0,
next: () => {
if (this.i < 3) {
return Promise.resolve({ value: lines[this.i++], done: false })
}
return Promise.resolve({ done: true })
}
}
}
} as any
})
const app = new App('myFile.txt')
let promise = app.start()
mockedReadStream.emit('open')
await expect(promise).resolves.toBe(undefined)
})
But following code is never reached
for await (const line of rl) {
...
}
Is there a way to mock readline.createInterface and then it works with the for await (const line of rl)?
The issue is: the async iterable object is not triggered during the test.
Solution, we can just use an array in the mock, like this:
jest.spyOn(readline, 'createInterface').mockImplementationOnce(() => {
return ['text1', 'text2'] as any
})
Since for await (const item of iterable) works for async iterable objects as well sync iterables. With sync iterables, they will be executed automatically.

Jest mocking google-cloud/storage typescript

I have been trying to mock the #google-cloud/storage for my implementation so that I could test it without having to hit the cloud-storge in gcp and so far it has all been in vain
I have tried to mock the node_module scope folder using the jest doc and that didnt work out
Hence I tried using below
This is my implementation class
import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
import * as fs from 'fs';
import pump from 'pump';
import pino from 'pino';
import * as _ from 'lodash';
import {
ENV_NAME_DEV,
GCLOUD_DATABASE_BUCKET_DEV,
GCLOUD_DATABASE_BUCKET_PROD,
GCLOUD_ENV_STR_BUCKET_NAME,
GCLOUD_STORED_FILE_NAME_DEV,
GCLOUD_STORED_FILE_NAME_PROD,
GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH,
GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH,
} from '../common/util/app.constants';
import { PinoLoggerServiceInstance } from '../common/services/pino.logger.service';
import { AppUtilServiceInstance } from '../common/services/app.util.service';
export const uploadEnvFiles = async (env_name: string) => {
const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);
return new Promise(async (res, rej) => {
// This just returns the Storage() instance with keyFileName and projectID
//of google cloud console being set so authentication takes place
const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
const bucketToUpload = GCLOUD_ENV_STR_BUCKET_NAME;
let uploadLocalFilePath;
let destinationBucketPath;
if (!AppUtilServiceInstance.isNullOrUndefined(env_name)) {
uploadLocalFilePath = ENV_NAME_DEV === env_name ? GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH : GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH;
destinationBucketPath = ENV_NAME_DEV === env_name ? GCLOUD_DATABASE_BUCKET_DEV : GCLOUD_DATABASE_BUCKET_PROD;
}
LOGGER.info('after authentication');
pump(
fs.createReadStream(uploadLocalFilePath),
str
.bucket(bucketToUpload)
.file(destinationBucketPath)
.createWriteStream({
gzip: true,
public: true,
resumable: true,
})
)
.on('error', (err) => {
LOGGER.error('Error occured in uploading:', err);
rej({ status: 'Error', error: err, code: 500 });
})
.on('finish', () => {
LOGGER.info('Successfully uploaded the file');
res({ status: 'Success', code: 201, error: null });
});
});
};
export const downloadEnvFiles = async (env_name): Promise<any> => {
const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);
return new Promise(async (res, rej) => {
const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
try {
const [files] = await str.bucket(GCLOUD_ENV_STR_BUCKET_NAME).getFiles();
const filteredFile =
ENV_NAME_DEV === env_name
? _.find(files, (file) => {
c
return file.name.includes(GCLOUD_STORED_FILE_NAME_DEV);
})
: _.find(files, (file) => {
return file.name.includes(GCLOUD_STORED_FILE_NAME_PROD);
});
res({
status: 'Success',
code: 200,
error: null,
stream: str
.bucket(GCLOUD_ENV_STR_BUCKET_NAME)
.file(filteredFile.name)
.createReadStream()
});
} catch (err) {
LOGGER.error('Error in retrieving files from gcloud:'+err);
rej({ status: 'Error', error: err, code: 500 });
}
});
};
This is my jest ts
bucket.operations.int.spec.ts
I've tried to include the mock inline
import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
const { Storage } = require('#google-cloud/storage');
const { Bucket } = require('#google-cloud/storage');
import { File } from '#google-cloud/storage';
import { mocked } from 'ts-jest/utils'
const fs = require('fs');
import * as path from 'path';
import pump from 'pump';
import * as BucketOperations from './bucket.operations';
import { GCLOUD_ENV_STR_BUCKET_NAME } from '../common/util/app.constants';
const { PassThrough } = require('stream');
const fsMock = jest.mock('fs');
// Here we are trying to mock pump with a function returned
// since pump () is the actual fucntion, we are mocking the function to return a value
// which is just a value of "on" eventlistener.. so we indicate that this will be substituted
// with another mocked function
jest.genMockFromModule('#google-cloud/storage');
jest.mock('#google-cloud/storage', () => {
const mockedFile = jest.fn().mockImplementation(() => {
return {
File: jest.fn().mockImplementation(() => {
return {
name: 'dev.txt',
createReadStream: jest
.fn()
.mockReturnValue(
fs.createReadStream(
path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt')
)
),
createWriteStream: jest
.fn()
.mockReturnValue(
fs.createWriteStream(
path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')
)
)
};
})
};
});
const mockedBUcket = jest.fn().mockImplementation(() => {
return {
Bucket: jest.fn().mockImplementation(() => {
return {
constructor: jest.fn().mockReturnValue('test-bucket'),
getFiles: jest.fn().mockReturnValue([mockedFile])
}
})
}
});
return {
Storage: jest.fn().mockImplementation(() => {
return {
constructor: jest.fn().mockReturnValue('test-storage'),
bucket: mockedBUcket,
file: mockedFile,
createWriteStream: jest.fn().mockImplementation(() =>
fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')))
};
})
};
});
jest.mock('pump', () => {
const mPump = { on: jest.fn() };
return jest.fn(() => mPump);
});
describe('Test suite for testing bucket operations', () => {
const mockedStorage = mocked(Storage, true);
const mockeddFile = mocked(File, true);
const mockeddBucket = mocked(Bucket, true);
function cancelCloudStorageMock() {
//mockCloudStorage.unmock('#google-cloud/storage');
mockedStorage.mockClear();
mockeddBucket.mockClear();
mockeddFile.mockClear();
jest.unmock('#google-cloud/storage');
jest.requireActual('#google-cloud/storage');
}
function cancelFsMock() {
jest.unmock('fs');
jest.requireActual('fs');
}
afterEach(() => {
jest.clearAllMocks();
//jest.restoreAllMocks();
});
test('test for uploadfiles - success', async (done) => {
cancelFsMock();
pump().on = jest.fn(function(this: any, event, callback) {
if (event === 'finish') {
callback();
}
return this;
});
const actual = await BucketOperations.uploadEnvFiles('dev');
expect(actual).toEqual(
expect.objectContaining({
status: 'Success',
code: 201,
})
);
done();
});
test('test downloadEnvFiles - success', async (done) => {
jest.unmock('fs');
const fMock = (File.prototype.constructor = jest.fn().mockImplementation(() => {
return {
storage: new Storage(),
bucket: 'testBucket',
acl: 'test-acl',
name: 'dev.txt',
parent: 'parent bucket',
};
}));
const bucketGetFilMock = (Bucket.prototype.getFiles = jest.fn().mockImplementation(() => {
return [fMock];
}));
// Get files should be an array of File from google-cloud-storage
//Bucket.prototype.getFiles = jest.fn().mockReturnValue([mockedFsConstructor]);
//Storage.prototype.bucket = jest.fn().mockReturnValue(new Storage());
const mockReadable = new PassThrough();
const mockWritable = new PassThrough();
jest.spyOn(fs, 'createReadStream').mockReturnValue(
fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'))
);
await BucketOperations.downloadEnvFiles('dev');
done();
});
});
This is the exception I end up with. Upon debugging I see that the mocked instances are trying to execute, but it doesn't execute the file method in Storage mock. This is not available in #google-cloud/storage but I did try to mock it. Is there a way to mock just the usage of google-cloud/storage using jest?
EDIT:
Here is the exception:
TypeError: str.bucket(...).file is not a function
at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:37:6
at Generator.next (<anonymous>)
at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:4:12)
at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:22:40
at new Promise (<anonymous>)
at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:22:9
Thanks to #ralemos. I was able to find the answer on how I mocked
Here is the complete implementation.
I've added a few more test stories as well
So jest.mock() esp the #google-cloud/storage modules, needs to be mocked in a different way. The Bucket of the Storage has all the details of the files in gcp storage, so that needs to be mocked first, I also mocked the File (this is of type #google-cloud/storage). Now I added the mockedFile to the mockedBucket and from there to the mockedStorage. I've also added all the methods and properties and implemented a mock for all of them.
There is a lodash node_module usage in my test file, so I mocked that implementation as well. Now everything works fine.
import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
const { Storage } = require('#google-cloud/storage');
const fs = require('fs');
import * as path from 'path';
import pump from 'pump';
import * as BucketOperations from './bucket.operations';
const { PassThrough } = require('stream');
const fsMock = jest.mock('fs');
const mockedFile = {
name: 'dev.txt',
createWriteStream: jest.fn().mockImplementation(() => {
return fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt'));
}),
createReadStream: jest.fn().mockImplementation(() => {
return fs.createReadStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'));
}),
};
jest.mock('lodash', () => {
return {
find: jest.fn().mockImplementation(() => {
return mockedFile;
})
};
});
const mockedBucket = {
file: jest.fn(() => mockedFile),
getFiles: jest.fn().mockImplementation(() => {
const fileArray = new Array();
fileArray.push(mockedFile);
return fileArray;
})
};
const mockedStorage = {
bucket: jest.fn(() => mockedBucket)
};
jest.mock('#google-cloud/storage', () => {
return {
Storage: jest.fn(() => mockedStorage)
};
});
jest.mock('pump', () => {
const mPump = { on: jest.fn() };
return jest.fn(() => mPump);
});
describe('Test suite for testing bucket operations', () => {
function cancelCloudStorageMock() {
jest.unmock('#google-cloud/storage');
jest.requireActual('#google-cloud/storage');
}
function cancelFsMock() {
jest.unmock('fs');
jest.requireActual('fs');
}
afterEach(() => {
jest.clearAllMocks();
//jest.restoreAllMocks();
});
test('test for uploadfiles - success', async (done) => {
pump().on = jest.fn(function(this: any, event, callback) {
if (event === 'finish') {
callback();
}
return this;
});
const actual = await BucketOperations.uploadEnvFiles('dev');
expect(actual).toEqual(
expect.objectContaining({
status: 'Success',
code: 201,
})
);
done();
});
test('test downloadEnvFiles - success', async (done) => {
jest.unmock('fs');
const downloadRes = await BucketOperations.downloadEnvFiles('dev');
expect(downloadRes).toBeDefined();
expect(downloadRes).toEqual(expect.objectContaining({code:200, status: 'Success'}));
done();
});
test('test for uploadfiles- failure', async (done) => {
cancelCloudStorageMock();
const bucketStorageSpy = jest
.spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
.mockImplementation(() => {
return new Storage({
projectId: 'testId',
keyFilename: path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'),
scopes: ['testScope'],
autoRetry: false,
});
});
const mockReadable = new PassThrough();
const mockWritable = new PassThrough();
fs.createWriteStream = jest.fn().mockReturnValue(mockWritable);
fs.createReadStream = jest.fn().mockReturnValue(mockReadable);
pump().on = jest.fn(function(this: any, event, callback) {
if (event === 'error') {
callback();
}
return this;
});
const actual = BucketOperations.uploadEnvFiles('prod');
expect(actual).rejects.toEqual(
expect.objectContaining({
status: 'Error',
code: 500,
})
);
expect(bucketStorageSpy).toHaveBeenCalledTimes(1);
done();
});
test('test download - make the actual call - rej with auth error', async (done) => {
cancelCloudStorageMock();
console.dir(Storage);
const mockReadable = new PassThrough();
const mockWritable = new PassThrough();
fs.createWriteStream = jest.fn().mockReturnValue(mockWritable);
fs.createReadStream = jest.fn().mockReturnValue(mockReadable);
const createGcloudAuthenticationBucketSpy = jest
.spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
.mockImplementation(() => {
return new Storage();
});
try {
await BucketOperations.downloadEnvFiles('dev');
} catch (err) {
expect(err.code).toBe(500);
expect(err.status).toBe('Error');
}
expect(createGcloudAuthenticationBucketSpy).toHaveBeenCalledTimes(1);
createGcloudAuthenticationBucketSpy.mockReset();
done();
});
});

Sinon/sandbox test says that function was never called

I want to write a unit test that checks to see if a function was called, but i'm getting the error:
submitDetails
submitDetails
sendEmail:
AssertionError: expected sendEmail to have been called exactly once, but it was called 0 times
From what I can see my function submitDetails.submitDetails clearly runs the function sendEmail.sendEmail but it's saying that it's never called. I've also tried just using 'spy.called' instead of calledOnce but I get the same result.
Test file:
const submitDetails = require('../src/scripts/submitDetails')
const sendEmail = require('../src/lib/sendEmail')
describe('submitDetails', function () {
let sandbox = null
before(() => {
sandbox = sinon.createSandbox()
})
afterEach(() => {
sandbox.restore()
})
describe('submitDetails', () => {
let mockParams, result
beforeEach(async () => {
sandbox.spy(sendEmail, 'sendEmail')
})
it('sendEmail', () => {
expect(sendEmail.sendEmail).to.have.been.calledOnce()
})
})
})
SubmitDetails.js (file that's being test)
const { sendEmail } = require('../lib/sendEmail')
const submitDetails = {}
submitDetails.submitDetails = query => {
return sendEmail(query)
}
module.exports = submitDetails
You didn't call submitDetails.submitDetails() method in your test case. Here is the working example:
sendEmail.ts:
module.exports = {
sendEmail() {}
};
submitDetails.ts:
const sendEmail = require('./sendEmail');
// #ts-ignore
const submitDetails = {};
// #ts-ignore
submitDetails.submitDetails = query => {
return sendEmail.sendEmail(query);
};
module.exports = submitDetails;
submitDetails.spec.ts:
import { expect } from 'chai';
import sinon, { SinonSandbox, SinonSpy } from 'sinon';
const submitDetails = require('./submitDetails');
const sendEmail = require('./sendEmail');
describe('submitDetails', () => {
let sandbox: SinonSandbox;
before(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
describe('submitDetails', () => {
let sendEmailSpy: SinonSpy;
beforeEach(() => {
sendEmailSpy = sandbox.spy(sendEmail, 'sendEmail');
});
it('sendEmail', () => {
submitDetails.submitDetails();
sandbox.assert.calledOnce(sendEmailSpy);
expect(sendEmailSpy.calledOnce).to.be.true;
});
});
});
Unit test result:
submitDetails
submitDetails
✓ sendEmail
1 passing (22ms)
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58058653

Promise.resolve return undefined from jest mock function

I have a function which makes API call and based on what would return by the first API call it makes the second API call. But the first API always returns undefined
getTotalCount = async () => {
const { showCountCallBack, showCount } = this.props;
try {
const response = await showCount();
const count = isEmpty(response.result);
if (count) {
console.log(" success");
} else {
showCountCallBack({ ...this.state });
}
} catch (e) {
console.log("error");
}
};
describe("component", () => {
let shallowComponent;
let shallowComponentInstance;
const showCountMock = jest.fn(() => Promise.resolve({ result: [] }));
const showCountCallBackMock = jest.fn(() => Promise.resolve({ result: [] }));
beforeEach(() => {
showCountMock.mockReset();
shallowComponent = shallowWithTheme(
<Component
showCount={showCountMock}
showCountCallBack={showCountCallBackMock}
/>
);
shallowComponentInstance = shallowComponent.instance();
});
it("viewMapping", () => {
shallowComponentInstance.getTotalCount();
expect(showCountMock).toHaveBeenCalledTimes(1);
expect(showCountCallBackMock).toHaveBeenCalledTimes(1);
});
});
After struggling for hours. I have found the cause. It was mockReset causing this issue.
It reset return value as well. so I just removed the mockReset from code. Better to use mockClear here

Resources