I am trying to mock copyFile and stat method from fs modules(fs.promises). But the mocked function is not being called and instead the original functions are called though the test cases pass.
The testing function code is:
jest.doMock('fs', () => ({
promises: {
copyFile: (src = 'source', dest = 'destination') =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: () =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false); // Probably wrong datatype
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction()
test('Testing', async () => {
expect(sample.demo()).toEqual(Promise.resolve(true));
});
});
Actual Code which needs to be tested:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/'
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName,fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
Can someone please help regarding where I am going wrong ? I had thought of using jest.mock but it was also giving error so I followed this link https://github.com/facebook/jest/issues/2567 which suggested to try doMock. If someone knows better way to handle this mock function, it would be great.
Thanks !
You can use jest.mock(moduleName, factory, options), and you didn't mock the method chain call correctly. You should use mockFn.mockReturnThis() to return this context to the caller.
E.g.
index.ts:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/';
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName, fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
index.test.ts
import { MainFunction } from './';
jest.mock('fs', () => ({
promises: {
copyFile: jest.fn().mockImplementation((src = 'source', dest = 'destination') => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: jest.fn().mockReturnThis(),
isFile: jest.fn().mockImplementation(() => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false);
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});
test result:
PASS examples/66429093/index.test.ts
Testing implementation
✓ Testing (10 ms)
console.log
Inside the mock function in stat method, please get executed, got frustrated
at Object.<anonymous> (examples/66429093/index.test.ts:12:15)
console.log
Inside the mock function in copyFile, please get executed, got frustrated C:/Users/Desktop/testing-file-dir/ /destination/
at Object.<anonymous> (examples/66429093/index.test.ts:7:15)
console.log
Promise { false } inside actual code
at MainFunction.<anonymous> (examples/66429093/index.ts:9:13)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.12 s, estimated 4 s
Based upon slideshowp2's solution, I had to do this change in order to avoid the error as stated in this https://github.com/facebook/jest/issues/2567.
The actual file remains same while test file changes to:
jest.mock('fs', () => {
const originalModule = jest.requireActual('fs'); // so as to not override other functions apart from below mentioned one's
return Object.assign({ __esModule: true }, originalModule, {
promises: {
copyFile: jest.fn().mockImplementation((src, dest) => {
// src, dest are parameters passed in copyFile from src to destination
let source = 'some source'; // sample source file
if (source === src) {
return true;
} else {
throw Error;
}
}),
stat: jest.fn().mockReturnThis(),
isFile: jest
.fn()
.mockImplementationOnce(() => { // I had series of test so for first one I wanted false hence this part, else we can remove this and directly use .mockImplementation()
return false;
})
.mockImplementation(() => {
return true;
}),
},
});
});
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});
Related
In the setup below, if I run the test as is, myFunc is not mocked when I debug into handler.
However, if instead I add this. in front of the myFunc call in handler, then the function is mocked and everything works as expected.
Can someone please explain why this is? I'm new to mocking and can't see it.
I know what this does, but why won't jest mock without it since I told it to mock that function in the module?
index.js
const aws = require('aws-sdk')
exports.handler = async function (event, context) {
let s;
switch (event.func) {
case "myFunc":
console.log('Executing myFunc');
//making the call: s = await this.myFunc.apply(null, [event.params]) will make the mock work.
s = await myFunc.apply(null, [event.params])
console.log(s);
return s;
/*cases...*/
default:
// default behaviour
}
async myFunc({p1, p2}){
/* do something */
return x
}
exports.myFunc = myFunc
}
index.spec.js
jest.mock('./index.js', () => {
const allAutoMocked = jest.createMockFromModule('./index.js')
const actual = jest.requireActual('./index.js')
return {
__esModules: true,
...allAutoMocked,
myFunc : jest.fn().mockImplementation(() => ({ mockedValue: 'test' })),
handler: actual.handler
}
})
let index = require("./index.js")
describe('Test myFunc', () => {
test('If myFunc function was called', async () => {
var event = { func: 'myFunc', params: { p1: xx, p2: false } };
const context = {};
const logMock = jest.fn((...args) => console.log(...args));
const data = await handler(event, context);
})
})
I am using the html-pdf package in my nodejs code (not in Typescript). Now, this package has a create() function which is chained with the toBuffer() function. I am unit testing my code using Jest and want to mock this call pdf.create(html).toBuffer().
var pdf = require('html-pdf');
pdf.create(html).toBuffer(function(htmlToPdfError, buffer){
if (htmlToPdfError) {
reject(htmlToPdfError);
}
resolve(buffer.toString('base64'));
});
EDIT:
I am trying to use the following code in my spec file to make the module:
jest.mock('html-pdf', () => ({
create: jest.fn(() => {
return Promise.resolve();
})
}));
This is helping me mock the create() function but I do not know how to return a object in Promise.resolve which would have a toBuffer function
I could mock it using the following code:
const mockToBuffer = {
toBuffer: jest.fn((callback: Function) => callback(null, null)),
}
jest.mock('html-pdf', () => ({
create: jest.fn(() => mockToBuffer),
}))
it('Should work', async () => {
const expectedResult = Buffer.from([10])
mockToBuffer.toBuffer.mockImplementation((callback: Function) => {
callback(null, expectedResult)
})
// const result = await yourFuncUsingHtmlPdf(/* fakePayload */)
// Comparing the buffer using the native function
// expect(expectedResult.equals(result)).toBe(true)
}
will this work?
and then assert that your "pdf" buffer contains "test string"?
jest.mock('html-pdf', () => ({
create: jest.fn(() => {
return Promise.resolve({
toBuffer: function(callback) {
callback(null, Buffer.from("test string", "utf-8"));
},
});
})
}));
(I haven't tried it)
Edited Question with vazsonyidl suggestions applied
I have to write unit tests for a function similar to this one:
import {External} from 'ExternalModule';
async functionA(){
this.functionB().then((data) => {
External.functionC(options);
console.log("Reached1");
}).catch((data) => {
const { OnError = "" } = data || {}
if(OnError) {
External.functionC(anotherOptions);
console.log("Reached2");
}
})
}
functionB() {
return new Promise(() => {
});
}
As functionC belongs to another module, I placed a mock of it in the _mocks_folder:
//_mocks_/ExternalModule.ts
export var External: ExternalClass = {
functionC(){}
}
class ExternalClass{
constructor(){};
functionC(){};
}
I have mocked functionB in two diferent ways for testing the then and the catch :
it("should test then block", () => {
functionB = jest.fn(() => {return Promise.resolve()});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
it("should test catch block", () => {
const err = { OnError: "Error" };
functionB = jest.fn(() => {return Promise.reject(err)});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
What I am trying to do is expect that functionC was called and called with the correct params, but the test is always passing even if I test if functionC was not called.
What am I doing wrong?
Jest does not wait for the async code to complete before doing assertions.
You can use the following function:
const waitForPromises = () => new Promise(setImmediate);
to force Jest to wait for promises to complete before continuing like so:
it("does something", async () => {
promiseCall();
await waitForPromises();
expect(something).toBe(something)
});
I think when this function catch error, this error should have an 'OnError' property so the functionC can run.
const { OnError = "" } = data || {}
if(OnError) {
ExternalClass.functionC(anotherOptions);
}
change you response error data to return Promise.reject({OnError: '404'}) may solve this problem.
Because you are not providing it to your class.
The following code is working for me:
class A {
async functionA() {
this.functionB().then((data) => {
this.functionC(); // It woll log aaa here, you need this one.
}).catch((data) => {
const {OnError = ''} = data || {};
if (OnError) {
console.log('onerror');
}
});
}
functionB() {
return new Promise(() => {
});
}
functionC() {
return 2;
}
}
describe('a', () => {
it('test', () => {
const a = new A();
a.functionB = jest.fn(() => Promise.resolve());
const functionBSpy = jest.spyOn(a, 'functionC');
void a.functionA().then(() => {
expect(functionBSpy).toHaveBeenCalledTimes(1);
});
});
});
Hope this helps, any comment appreciated.
As you provided no information about your functionB I mocked something that may suitable for you.
Your original problem is that Jest does not wait for your callbacks to settle. It does the assertion although, even if your function calls happen later, Jest will not recognise them and says that no call ever occurred.
There are several docs available, for example Jest's one here
Hi guys I'm having trouble testing the below JS using Jest. It starts with waitForWorker. if the response is 'working' then it calls waitForWorker() again. I tried Jest testing but I don't know how to test an inner function call and I've been researching and failing.
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
Some of my test is below since i can't double check with anyone. I don't know if calling await Worker.checkWorkerStatus() twice in the tests is the best way since waitForWorker should call it again if the response data.status is 'working'
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
I think there are a couple things you can do here.
Inject status handlers
You could make the waitForWorker dependencies and side effects more explicit by injecting them into the function this lets you fully black box the system under test and assert the proper injected effects are triggered. This is known as dependency injection.
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
Now to test, you really just need to create mock functions.
const onComplete = jest.fn();
const onBusy = jest.fn();
And assert that those are being called in the way you expect. This function is also async so you need to make sure your jest test is aware of the completion. I notice you are using async in your test, but your current function doesnt return a pending promise so the test will complete synchronously.
Return a promise
You could just return a promise and test for its competition. Right now the promise you have is not exposed outside of waitForWorker.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
The above example converts your function to async for readability and removes side effects. I returned an async result with a status, this is usefull since there are many branches that waitForWorker can complete. This will tell you that given your axios setup that the promise will complete eventually with some status. You can then use coverage reports to make sure the branches you care about were executed without worrying about testing inner implementation details.
If you do want to test inner implementation details, you may want to incorporate some of the injection principals I mentioned above.
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
You can then inject any function into this, even a mock and make sure its called the way you want without having to mock up axios. In your application you simply just inject checkWorkerStatus.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}
I'm using TypeScript to write a very simple service that utilizes the AWS SDK. My Jest unit tests are passing, but the coverage reports are saying that the line 'return result.Items' is not covered. Can anyone tell why this is? Is it a bug in jest?
// service file
/**
* Gets an array of documents.
*/
function list(tableName) {
const params = {
TableName: tableName,
};
return docClient
.scan(params)
.promise()
.then((result) => {
return result.Items;
});
}
// test file
const stubAwsRequestWithFakeArrayReturn = () => {
return {
promise: () => {
return { then: () => ({ Items: 'fake-value' }) };
},
};
};
it(`should call docClient.scan() at least once`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
it('should return result.Items out of result', async () => {
const mockAwsCall = jest
.fn()
.mockImplementation(stubAwsRequestWithFakeArrayReturn);
aws.docClient.get = mockAwsCall;
const returnValue = await db.get('fake-table', 'fake-id');
expect(returnValue).toEqual({ Items: 'fake-value' });
});
The line not covered is the success callback passed to then.
Your mock replaces then with a function that doesn't accept any parameters and just returns an object. The callback from your code is passed to the then mock during the test but it doesn't call the callback so Jest correctly reports that the callback is not covered by your tests.
Instead of trying to return a mock object that looks like a Promise, just return an actual resolved Promise from your mock:
const stubAwsRequestWithFakeArrayReturn = () => ({
promise: () => Promise.resolve({ Items: 'fake-value' })
});
...that way then will still be the actual Promise.prototype.then and your callback will be called as expected.
You should also await the returned Promise to ensure that the callback has been called before the test completes:
it(`should call docClient.scan() at least once`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
The Library chai-as-promised is worth looking at.
https://www.chaijs.com/plugins/chai-as-promised/
Instead of manually wiring up your expectations to a promise’s
fulfilled and rejected handlers.
doSomethingAsync().then(
function (result) {
result.should.equal("foo");
done();
},
function (err) {
done(err);
}
);
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("foo");