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);
});
Related
I want to use mocha to run some basic post-deploy tests against live services. I want to programmatically load tests based upon a config object passed to the test script.
The problem I'm encountering is that mocha.run() executes and exits the node process without continuing with the rest of the code. It is not clear to me how to force node to wait for the mocha result and continue with the rest of the code.
mocha-setup.js
const Mocha = require('mocha');
const util = require('util');
const { Test, Suite } = Mocha;
const timeoutMillis = 300000; // five minutes
const mocha = new Mocha({
timeout: timeoutMillis,
reporter: 'mochawesome',
reporterOptions: {
reportDir: '../reports/unit'
},
color: true,
});
const makeSuite = (suiteName) => Suite.create(mocha.suite, suiteName);
const runMochaTests = async (tests, config) => {
const suite = makeSuite('My programmatic test suite');
tests.forEach(({ test, statement }) => {
suite.addTest(new Test(statement, async () => {
await test(config);
}));
});
console.log('This logs as expected.');
const promisifyMocha = util.promisify(() => mocha.run());
const result = await promisifyMocha(); // code seems to exit here
console.log('I am never logged');
console.log(result); // also never logged
// `result` is never returned, and higher level code implementing `runMochaTests` does not continue either
return result; // Eventually, I want to return the number of failures
};
module.exports = {
runMochaTests,
};
The plan is for runMochaTests to return the number of failures. runMochaTests() will be used by different higher order code modules as necessary.
Lastly, in my best life, I would not have to manually promisify mocha at all. If there was a way to use something like const result = await mocha.run(), that feels like the best implementation.
My problem was that Mocha is fundamentally about event listeners, not promises. Using this reference code, I ended up with...
const runMochaTests = async (tests, config) => new Promise((resolve, reject) => {
const testResults = [];
let numberOfFailures = 0;
try {
const suite = makeSuite('Verify Regional Apps');
tests.forEach(({ test, statement }) => {
suite.addTest(new Test(statement, async () => {
await test(config);
}));
});
const runner = mocha.run();
runner.on('test end', (testResult) => {
if (testResult.state === 'failed') numberOfFailures += 1;
testResults.push(testResult);
});
runner.on('end', () => {
runner.removeAllListeners('test end');
runner.removeAllListeners('end');
ClearModule.all(); // Needed to ensure all tests are executed every execution.
resolve(numberOfFailures);
});
} catch (err) {
reject(err);
}
});
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.
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
})
I've got a node application that spawns a child_process. When the child_process is finished running, I'd like to resolve a promise. The following code works, but the .then() statements occur out of order:
const storage = require('./storage');
const logging = require('./logging');
const process = require('child_process').spawn;
function convertIncomingFile(pathToFile) {
logging.info(`Converting ${pathToFile}`);
const convert = process(`cat`, [pathToFile], {});
return Promise.resolve(
convert.stdout.on('data', (data) => {
logging.info(data.toString('utf8'));
}),
convert.stderr.on('data', (err) => {
logging.error(err);
}),
convert.on('close', (code) => {
logging.info(`Conversion finished with status code ${code}`);
})
);
}
module.exports = {
convertFile: (filename) => {
storage.downloadFile(filename).
then((localFilename) => {
logging.info(`File saved to: ${localFilename}`);
}).
then(() => convertIncomingFile(`./files/${filename}`)).
then(() => {
logging.info(`Coversion of ${filename} complete.`);
}).
catch((apiErr) => {
logging.error(apiErr);
});
}
};
The output I get is:
info: File saved to: ./files/package.json
info: Converting ./files/package.json
info: Coversion of package.json complete.
info: {
<file contents>
}
info: Conversion finished with status code 0
As you can see the Conversion of package.json complete. statement occurs before the file contents are logged and the conversion status code statement. Why is this the case and how do I get the 'Conversion complete' statement to come after the 'status code' statement?
Promise.resolve means return a solved value that you give it, it's not realy async as you expected. Check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more detailed info
function convertIncomingFile(pathToFile) {
logging.info(`Converting ${pathToFile}`);
const convert = process(`cat`, [pathToFile], {});
return new Promise((resolve, reject) => {
convert.stdout.on('data', (data) => {
logging.info(data.toString('utf8'));
}),
convert.stderr.on('data', (err) => {
logging.error(err);
reject()
}),
convert.on('close', (code) => {
logging.info(`Conversion finished with status code ${code}`);
resolve()
})
})
}
You have to pass the convertFile promise further to let next then know that it has to wait:
then(() => {
return convertFile(`./files/${filename}`);
})
and shorter equivalent:
then(() => convertFile(`./files/${filename}`))
I've been trying to test the following code using Mocha, but I always get the error.
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test
The code I want to test is as follows.
'use strict'
const Promise = require('bluebird');
const successResponse = {status: 'OK'};
const failResponse = {status: 'FAIL'};
function dbStatusSuccess () {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(successResponse);
}, 2010);
});
}
function dbStatusFail () {
return new Promise(function(resolve, reject) {
setTimeout(() => {
reject(failResponse);
}, 2000);
});
}
module.exports = {
dbStatusSuccess,
dbStatusFail
}
and here are my tests.
'use strict'
const Promise = require('bluebird');
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
const resp = {status:'OK'};
const resp2 ={status: 'FAIL'};
describe('healthy-check end point', () => {
it('should return successful response when connected to database', () => {
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
}).catch( (err) => {
console.log(err);
return expect(err).to.deep.equal(resp2);
});
});
});
I also get an error { AssertionError: expected { status: 'OK' } to equal { status: 'OK' } in the console. Which I believe is from loggin the err from the .catch function.
EDIT 1.
Removed the reject function from dbStatusSuccess function.
The issue is with the promises taking 2 seconds to complete/fail. If the time set in setTimeout is less than 2 seconds, the tests will pass.
The default timeout in your test seems to be 2000ms. Your code obviously takes longer to complete. Therefore, you have to up the timeout limit. As noted here you shouldn't use arrow functions so you can safely access this.
Then you can increase your timeout like so:
'use strict'
const Promise = require('bluebird');
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
const resp = {status:'OK'};
const resp2 ={status: 'FAIL'};
describe('healthy-check end point', () => {
it('should return successful response when connected to database', function() {
this.timeout(3000);
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
}).catch( (err) => {
console.log(err);
return expect(err).to.deep.equal(resp2);
});
});
});
Then your test should run as expected.
'use strict'
const Promise = require('bluebird');
const chai = require('chai');
chai.use(require('chai-string'));
chai.use(require('chai-as-promised'));
const expect = chai.expect;
chai.should();
const healthyCheck = require('./healthyCheck');
describe('healthy-check end point', function() {
it('should return successful response when connected to database', function(done) {
const resp = {status: 'OK'};
healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
expect(res).to.equal(resp);
done();
}).catch(done);
});
});
In code example I don't return promise, so I need use callback. Usage callback in your async tests helps avoid Error: timeout of 2000ms exceeded
Don't use arrow function in descibe and it. More info
You should use the done callback, for example:
it('reads some file', function(done) {
fs.readFile('someFile.json', function(err, data) {
if (err) return done(err);
assert(data != null, "File should exist.");
done();
});
});
What's happening is the test ('it' function) returns before your promise resolves; using done means the test won't finish until you call done() when the promises resolves.
See
http://tobyho.com/2015/12/16/mocha-with-promises/
and
https://mochajs.org/#working-with-promises
Well, I just found the problem, your test is tricky.
You set the timeout timer to 2010ms but Mocha default execution time is 2000ms, so you will always get the error from Mocha.
I still think you shouldn't create .catch block in the returned promise chain, it will stop the promise chain to propagate.
describe('healthy-check end point', () => {
it('should return successful response when connected to database', () => {
return healthyCheck.dbStatusSuccess()
.then((res) => {
console.log(JSON.stringify(res, undefined, 2));
return expect(res).to.equal(resp);
});
}).timeout(2500); //tell Mocha to wait for 2500ms
});