Mocking npm package with different returns - javascript

I'm trying to mock an npm package implementation, both to return a Promise that resolves to true and for another test, I want to reject with an error.
At the top of the test file, before the first test description, I define the following code to mock an npm package:
const mockNewFile = (): File => new File(['this is new file content'], 'new-file');
jest.mock('blueimp-load-image', () => () => {
const newFile = mockNewFile();
return new Promise(resolve => {
const data = {
image: {
toBlob: (func: (file: File) => File) => {
func(newFile);
}
}
};
resolve(data);
});
});
With this, I'm able to run my tests successfully for a function that relies on this npm package, called blueimp-load-image
But then I wanted to add a test for what should happen if this blueimp-load-image function fails, that is, when the promise it returns is rejected.
To do this I created a new description block within the main description block of the test file and tried to mock the npm package again there, by having it return a different:
describe('if loadImage returns an error', () => {
beforeEach(() => {
jest.mock('blueimp-load-image', () => () => {
return new Promise((resolve, reject) = reject(new Error('something went wrong')));
});
});
test('return the file back unmodified', async () => {
const expected = {/* file content */};
const result = await theFunctionUsingLoadImage(file);
expect(result).toStrictEqual(expected);
});
});
The test above here fails because no error seems to be thrown, leading me to expect that the mock created in the beforeEach block is not working. I know this because the expected and result should be the same, it would only differ if there was no error.
I've tried to figure this out as well using jest.spyOn instado of Jest.mock but that attempt was a complete failure.
What am I missing?

Jest.mock can be called only once, and should implement the entire interface of the mocked lib.
There are several possible options:
Using mocks, which allows you to write a mock implementation which can expose additional (mock only) methods.
Using jest.mock with global variable which will control if the mock should return success or reject.
This is an example of the second option
const mockNewFile = (): File => new File(['this is new file content'], 'new-file');
let shouldSuccess = true;
jest.mock('blueimp-load-image', () => () => {
const newFile = mockNewFile();
return new Promise((resolve, reject) => {
if(!shouldSuccess) {
return reject('data-of-reject');
}
const data = {
image: {
toBlob: (func: (file: File) => File) => {
func(newFile);
}
}
};
resolve(data);
});
});
Now all you need to do is to change the value of shouldSuccess to false, in order to make your mock implementation to reject.

Related

Why isn't my jest mock function implementation being called?

I have the following jest test configuration for my collection of AWS JS Node Lambdas. I have a module called dynamoStore I reference in several different lambdas package.json and use within the lambdas. I am trying to get test one of these lambdas by mocking the dynamo store module as it makes calls to dynamoDb. The problem is that the jest.fn implementation never gets called. I confirmed this by sticking a breakpoint in that line as well as logging the value the calling methods returns from it.
When I check lambda1/index.js in the debugger getVehicleMetaKeysFromDeviceId() is a jest object but when it is called it doesn't use my mock implementation
How do I get this implementation to work? Have I set up my mock incorrectly?
dynamoStore/vehicleMetaConstraints
exports.getVehicleMetaKeysFromDeviceId= async (data) => {
return data
};
dynamoStore/index.js
exports.vehicleMetaConstraints = require("./vehicleMetaConstraints");
...
lambda1/index.js
const { vehicleMetaStore } = require("dynamo-store");
exports.handler = async (event, context, callback) => {
const message = event;
let vehicle_ids = await vehicleMetaStore.getVehicleMetaKeysFromDeviceId(message.id);
// vehicle_ids end up undefined when running the test
}
lambda1/index.test.js
const { vehicleMetaStore } = require("dynamo-store");
jest.mock("dynamo-store", () => {
return {
vehicleMetaStore: {
getVehicleMetaKeysFromDeviceId: jest.fn(),
},
};
});
describe("VehicleStorageLambda", () => {
beforeEach(() => {
jest.resetModules();
process.env = { ...env };
});
afterEach(() => {
jest.clearAllMocks();
});
test("Handles first time publish with existing device", async () => {
let functionHandler = require("./index");
vehicleMetaStore.getVehicleMetaKeysFromDeviceId.mockImplementationOnce(() =>
// This never gets called
Promise.resolve({
device_id: "333936303238510e00210022",
})
);
await functionHandler.handler({});
});
});
Remove the call to jest.resetModules() in beforeEach. That's re-importing your modules before each test, and wiping out your mocks.
https://stackoverflow.com/a/59792748/3084820

How to fix ajv schema not being checked correctly while testing with Jest

Basically I am currently writing a unit test for a function which checks if a json-file is valid, using an AJV Schema. The problem is, that the checking against the schema works in the browser, but not in the test.
InvalidFileError
export class InvalidFileError extends Error {
constructor(message) {
super(message)
this.name = "InvalidFileError"
}
}
The function I'm trying to test
export function importFile(e, importScenarios, importDevices) {
const file = e.target.files[0]
const fileReader = new FileReader()
fileReader.readAsText(file)
fileReader.onload = () => { // File loaded
const fileContent = JSON.parse(fileReader.result)
const ajv = new Ajv({allErrors: true})
const validate = ajv.compile(schema)
const contentIsValid = validate(fileContent)
console.log("Content is valid: ", contentIsValid)
if (contentIsValid) {
importScenarios(fileContent.scenarios)
importDevices(fileContent.devices)
} else {
throw new InvalidFileError("This file doesn't match the schema")
}
}
}
The current test I have written
describe("Does Importing a file work properly?", () => {
let file
let e = {
target: {
files: []
}
}
let importScenarios = () => {}
let importDevices = () => {}
test("Does it work with a file matching the schema?", () => {
file = new Blob(correctTestContent, { type: "application/json" })
e.target.files.push(file)
expect(() => {
FileManager.importFile(e, importScenarios, importDevices)
}).not.toThrow(InvalidFileError)
})
test("Does it work with a file not matching the schema??", () => {
file = new Blob(incorrectTestContent, { type: "application/json" })
e.target.files.push(file)
expect(() => {
FileManager.importFile(e, importScenarios, importDevices)
}).toThrow(InvalidFileError)
})
afterEach(() => {
e.target.files = []
})
})
When I use this function in the browser, by uploading an invalid file, it throws an error, and it if i upload a valid file, it does not.
This should be the exact same in the test, but unfortunately it is not.
The problem is that the code you are trying to test is asynchronous, while the tests you have written are not.
When you run the tests, the onload callback of the FileReader is not being executed during the execution of the corresponding test. Instead, it is being called after the test has executed. In fact, because you have the statement:
console.log("Content is valid: ", contentIsValid)
inside the importFile method, you should be seeing in console a message like this one:
Cannot log after tests are done. Did you forget to wait for something async in your test?
You need to make your tests asynchronous, so that they wait for the onload callback execution. Unfortunately, your code is difficult to test as it is, because you have no way to know when the onload callback has been executed so it is also difficult to wait in the test until that moment.
One way to solve this problem would be to wrap your asynchronous code in a Promise and return it so that we can wait until the promise is finished. With this approach, your importFile would look something like:
export function importFile(e, importScenarios, importDevices) {
const file = e.target.files[0]
const fileReader = new FileReader()
fileReader.readAsText(file)
return new Promise((resolve, reject) => {
fileReader.onload = () => { // File loaded
const fileContent = JSON.parse(fileReader.result)
const ajv = new Ajv({allErrors: true})
const validate = ajv.compile(schema)
const contentIsValid = validate(fileContent)
if (contentIsValid) {
importScenarios(fileContent.scenarios)
importDevices(fileContent.devices)
resolve()
} else {
reject(new InvalidFileError("This file doesn't match the schema"))
}
}
});
}
Then, you can test this method by returning the Promise in the test (so that jest knows that it has to wait until the promise is resolved or rejected):
let importScenarios = jest.fn()
let importDevices = jest.fn()
test("Does it work with a file matching the schema?", () => {
expect.assertions(2);
file = new Blob(correctTestContent, { type: "application/json" })
e.target.files.push(file)
return FileManager.importFile(e, importScenarios, importDevices).then(() => {
expect(importScenarios).toHaveBeenCalledTimes(1);
expect(importDevices).toHaveBeenCalledTimes(1);
});
});
test('Does it work with a file not matching the schema??', () => {
expect.assertions(1);
file = new Blob(incorrectTestContent, { type: "application/json" })
e.target.files.push(file)
return FileManager.importFile(e, importScenarios, importDevices).catch((e) => {
expect(e).toBeInstanceOf(InvalidFileError);
});
});
Note that I have redefined the variables importScenarios and importDevices so that they are mock functions and we can check if they are called. Also, note the use of expect.assertions to verify that a certain number of assertions are called.
Lastly, take into account that if you redefine your importFile so that it returns a promise, you'll likely have to change the places where you call it to treat the rejection case. Where you have:
try {
FileManager.importFile(e, importScenarios, importDevices)
} catch(e) {
// Some treatment of your exception
}
you will need:
FileManager.importFile(e, importScenarios, importDevices).catch(e => {
// Some treatment of your exception
})

How to mock this method?

I want to mock the below line of code. And please explain how I can mock this in detail as I'm new to javascript and writing test cases. The below code would return a promise.
const createPriceConfiguration = (fastify, req) => {
return fastify.pg.transact(client => insertQuery(fastify, client, req));
};
const client = {
query: jest.fn(() => {
return new Promise(resolve => {
resolve({ rows: [req.body] });
});
})
};
My colleague gave a solution which I'm not able to understand.
transact: jest.fn(queryFunction => {
return queryFunction(client);
})
You want to test createPriceConfiguration function which takes fastify object and calls function from it. Mocking this function can be done by mocking the fastify object. You need to mock the transact method in the fastify object passed to return desired response (e.g. promise or result of an other function, ...)
const mockedFastify = {
pg: {
transact: jest.fn(() => new Promise(resolve => {
...desired code
}))
}
};
Then in test case You pass mocked object createPriceConfiguration(mockedFastify);

Jest: restore original module implementation on a manual mock

I have a pretty common testing use case and I am not sure what's the best approach there.
Context
I would like to test a module that depends on a userland dependency. The userland dependency (neat-csv) exports a single function that returns a Promise.
Goal
I want to mock neat-csv's behavior so that it rejects with an error for one single test. Then I want to restore the original module implementation.
AFAIK, I can't use jest.spyOn here as the module exports a single function.
So I thought using manual mocks was appropriated and it works. However I can't figure it out how to restore the original implementation over a manual mock.
Simplified example
For simplicity here's a stripped down version of the module I am trying to test:
'use strict';
const neatCsv = require('neat-csv');
async function convertCsvToJson(apiResponse) {
try {
const result = await neatCsv(apiResponse.body, {
separator: ';'
});
return result;
} catch (parseError) {
throw parseError;
}
}
module.exports = {
convertCsvToJson
};
And here's an attempt of testing that fails on the second test (non mocked version):
'use strict';
let neatCsv = require('neat-csv');
let { convertCsvToJson } = require('./module-under-test.js');
jest.mock('neat-csv', () =>
jest.fn().mockRejectedValueOnce(new Error('Error while parsing'))
);
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = (promise) =>
promise.then(
(value) => {
throw value;
},
(reason) => reason
);
test('mocked version', async () => {
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
});
test('non mocked version', async () => {
jest.resetModules();
neatCsv = require('neat-csv');
({ convertCsvToJson } = require('./module-under-test.js'));
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual(
'[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]'
);
});
I am wondering if jest is designed to do such things or if I am going the wrong way and should inject neat-csv instead ?
What would be the idiomatic way of handling this ?
Yes, Jest is designed to do such things.
The API method you are looking for is jest.doMock. It provides a way of mocking modules without the implicit hoisting that happens with jest.mock, allowing you to mock in the scope of tests.
Here is a working example of your test code that shows this:
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = promise =>
promise.then(value => {
throw value;
}, reason => reason);
test('mocked version', async () => {
jest.doMock('neat-csv', () => jest.fn().mockRejectedValueOnce(new Error('Error while parsing')));
const neatCsv = require('neat-csv');
const { convertCsvToJson } = require('./module-under-test.js');
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
jest.restoreAllMocks();
});
test('non mocked version', async () => {
const { convertCsvToJson } = require('./module-under-test.js');
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual('[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]');
});

How to test error for a stream in nodejs?

I have a function which create a md5 and I have created a test which check its behavior, the script works. Now I need to create a test which check the promise is rejected when createHash() or createReadStream() fails.
How to test this scenario, any best practices? I would appreciate if you could post a sample, thanks!
export const md5 = (path: string) =>
new Promise<string>((resolve, reject) => {
const hash = createHash("md5");
const rs = createReadStream(path);
rs.on("error", reject);
rs.on("data", chunk => hash.update(chunk));
rs.on("end", () => resolve(hash.digest("hex")));
});
describe("md5", () => {
const fileName = `${TEST_DIR}/file1.txt`;
beforeAll(() => createFile(fileName));
afterAll(() => removeFile(TEST_DIR));
it("should hash md5 a file", () => {
md5(fileName).then((hash: string) => {
assert.strictEqual(hash, "4738e449ab0ae7c25505aab6e88750da");
});
});
});
I need to create a test which check the promise is rejected
Try the code below: the 2nd parameter of a Jasmine it block is a function that has a parameter done passed to it. done is a function that the user can invoke to make the test pass. If done is not invoked within the timeout window, Jasmine considers the test a failure.
stream-reader.js
const fs = require('fs')
const createReader = inputFile => {
const reader = fs.createReadStream(inputFile, {encoding: 'utf8'})
const result = new Promise((resolve, reject) => {
reader.on('error', e => reject(e))
})
// return 2 things...
return {
reader, // ...a stream that broadcasts chunks over time
result, // ...a Promise to be resolved on reading is done or error encountered
}
}
module.exports = createReader
spec.js
const streamReader = require('../stream-reader')
const INPUT_FILE = './input1.txt'
describe('streamReader', () => {
it(`should pass a chunk of the file on 'data' event`, (done) => {
const api = streamReader(INPUT_FILE)
api.reader.on('data', (chunk) => {
console.log('[JASMINE TEST 1] received', chunk)
done()
})
})
/* test Promise.reject on stream error */
it(`should reject on case of reading errors`, (done) => {
const api = streamReader('./non/existent/file')
api.result.catch(e => {
console.log('[JASMINE TEST 2] correctly rejected')
done()
})
})
})
output
$ npm test
> so-jasmine-test#1.0.0 test C:\Users\jonathanlopez\nodejs\so-stream-reject
> jasmine
Randomized with seed 22758
Started
[JASMINE TEST 1] received the quick brown fox
.[JASMINE TEST 2] correctly rejected
.
2 specs, 0 failures
Finished in 0.026 seconds
Randomized with seed 22758 (jasmine --random=true --seed=22758)
Hope this helps.
Cheers.
You could your md5 function for error in this way
md5('bad_path').catch((error: Error) => {
assert.strictEqual(error.message.length > 0, true);
});

Categories

Resources