(This seems partially overlapping with my previous question: Test interaction with two interval async functions)
Using jest 27.5.1 and sinon 13.0.1, I want to unit-test an async interval function.
Consider this code:
const fs = require("fs")
const { exec } = require("child_process")
module.exports = class MyClass {
init() {
setInterval(this.checkFileContent, 10)
}
async checkFileContent() {
await new Promise((resolve) => { exec("sleep 0.4", resolve) })
const fileContent = (await fs.promises.readFile("foo.txt")).toString()
console.log(fileContent)
}
}
and this test file
const MyClass = require(".")
const fs = require("fs")
const sinon = require("sinon")
const { exec } = require("child_process")
describe("MyClass", () => {
let sandbox
beforeAll(async () => {
sandbox = sinon.createSandbox()
await fs.writeFileSync("foo.txt", "fooContent")
})
afterAll(async () => {
await fs.unlinkSync("foo.txt")
await new Promise((resolve) => { exec("sleep 0.8", resolve) })
sandbox.verifyAndRestore()
})
test("should test MyClass", async () => {
const clock = sandbox.useFakeTimers()
const instance = new MyClass()
instance.init()
clock.next()
await Promise.resolve() // As suggested in this answer https://stackoverflow.com/a/52196951/2710714 - but doesn't seem to help at all
clock.reset()
})
})
Running the test, I'll always get ENOENT: no such file or directory, open 'foo.txt'. The reason being that await fs.unlinkSync("foo.txt") has already run when await fs.promises.readFile("foo.txt") is reached.
So, I need to somehow make sure that all promises in the interval function started by clock.next() are resolved before the tests stops. How do I do that?
Note: In real-life, it's not about file access but database queries. And the exec("sleep ..." is of course something artificial to replicate long-lasting operations. But the principles should be the same.
Edit: Also tried await clock.nextAsync() - didn't help.
Related
I need to test an async Node.js library which periodically generates events (through EventEmitter) until done. Specifically I need to test data object passed to these events.
The following is an example using mocha + chai:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', async () => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler); // This handler should be called/tested multiple times
await asyncLib.start(); // Will generate several 'events' until done
await asyncLib.close();
});
});
The problem is that even in case of an AssertionError, mocha marks the test as passed and the program terminates with exit code 0 (instead of 1 as I expected).
The following uses done callback instead of async syntax, but the result is the same:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', (done) => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler);
asyncLib.start()
.then(asyncLib.close)
.then(() => done());
});
});
I have also tried with a "pure" Node.js approach using the native assert.ok without any 3rd part library:
const { strict: assert } = require('assert');
const { AsyncLib } = require('async-lib');
const test = async () => {
const onDataHandler = (data) => {
assert.ok(data.foo != null);
assert.ok(data.bar != null);
assert.ok(data.bar.length > 0);
};
asyncLib.on('event', onDataHandler);
const asyncLib = new AsyncLib();
await asyncLib.start();
await asyncLib.close();
}
(async () => {
await test();
})();
Even in this case, an AssertionError would make the program to terminate with exit code 0 instead of 1.
How can I properly test this code and make the tests correctly fail in case of an assertion error?
There are some things that you need to fix to make it works:
Make your test async, because the test is going to execute the expects after a certain event is received meaning it's going to be asyncronous.
Your event handler in this case onDataHandler should receive the done callback because there is the way how you can indicate to mocha that the test was finished successful as long as the expects don't fail.
I wrote some code and tested it out and it works, you have to make some changes to adapt your async library though:
describe('Test suite', function () {
const onDataHandler = (data, done) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
done();
};
it('test 1', async function (done) {
eventEmitter.on('event', (data) => onDataHandler(data, done));
setTimeout(() =>{
eventEmitter.emit('event', {
})
}, 400)
});
});
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 have some test code like this:
test('Test', async () => {
const someData = await setup()
const actual = myFunc(someData.x)
expect(actual.a).toEqual(someData.y)
expect(actual.b).toEqual(someData.y)
... many more like this
}
I would like to break apart the code into multiple test blocks (because I can't even add a description message to each expect statement).
If Jest supported async describe, I could do this:
describe('Some group of tests', async () => {
const someData = await setup()
test('Test1', async () => {
const actual = myFunc(someData.x)
expect(actual.a).toEqual(someData.y)
}
test('Test2', async () => {
const actual = myFunc(someData.x)
expect(actual.b).toEqual(someData.y)
}
})
I could duplicate the setup call for each test of course, but that would slow down the test considerable (I'm populating MongoDB there).
So, any way I can improve the structure of my test with Jest?
It's correct that describe callback function isn't supposed to be asynchronous. It synchronously defines tests for a suite, any asynchronous operations in its scope will be discarded.
Previously Jasmine and Jest allowed to access common test context with regular functions and this. This feature was deprecated in Jest; common variables need to be user-defined.
Shared code can be moved into helper function that internally uses beforeAll, beforeEach, etc:
const setupWithTestContext = (testContext = {}) => {
beforeAll(async () => {
const setupData = await setup();
Object.assign(testContext, setupData);
});
return testContext; // sets up a new one or returns an existing
});
const anotherSetupWithTestContext = (testContext = {}) => {
beforeEach(() => {
testContext.foo = 0;
});
return testContext;
});
...
describe('Some group of tests', async () => {
const sharedTestData = setupTestContext();
// or
// const sharedTestData = {}; setupTestContext(sharedTestData);
anotherSetupWithTestContext(sharedTestData);
test('Test1', async () => {
// context is filled with data at this point
const actual = myFunc(sharedTestData.x)
...
}
...
})
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"}]');
});
I'm a novice with Javascript and am struggling to understand how or at least, how best to return array values to another script to assert agains their values.
The context is I want to use Puppeteer to obtain some string values from WebElement attributes and then use the Chai expect library to assert for correct values( or otherwise).
The code I have thus far is:
//app.spec.js
const clothingChoice = await frame.$eval('#option-clothing-5787', e => e.getAttribute('value'));
const groceryChoice = await frame.$eval('#option-clothing-4556', e => e.getAttribute('value'));
const wineChoice = await frame.$eval('#option-clothing-4433', e => e.getAttribute('value'));
const voucherChoice = await frame.$eval('#option-clothing-3454', e => e.getAttribute('value'));
function testFunction() {
return new Promise(function(resolve, reject) {
resolve([clothingChoice, groceryChoice, wineChoice, voucherChoice]);
});
}
async function getChosenItemValues() {
const [clothingChoice, groceryChoice, wineChoice, voucherChoice] = await testFunction();
console.log(clothingChoice, groceryChoice, wineChoice, voucherChoice);
}
getChosenItemValues();
module.exports = getChosenItemValues;
};
I simply need to understand how to import the values that are currently simply printed out as:
1|clothing|option 1|grocery|option 1|wine|option 1|voucher|option
...into another file test.js in which I want to use chai to assert for their presence like so:
expect(clothingChoice).to.equal('1|clothing|option');
Try this:
// app.spec.js (.spec is normally reserved for test files, you may to change the name to avoid confusion)
const clothingChoice = frame.$eval('#option-clothing-5787', e => e.getAttribute('value'));
const groceryChoice = frame.$eval('#option-clothing-4556', e => e.getAttribute('value'));
const wineChoice = frame.$eval('#option-clothing-4433', e => e.getAttribute('value'));
const voucherChoice = frame.$eval('#option-clothing-3454', e => e.getAttribute('value'));
async function getChosenItemValues() {
return await Promise.all([clothingChoice, groceryChoice, wineChoice, voucherChoice]);
}
module.exports = {
getChosenItemValues
};
NOTE: does frame.$eval definitely return a Promise? I've not had any experience with Puppeteer.
// test file
const app = require('/path/to/app.spec.js');
describe('Suite', function() {
it('Returns expected values', function(done) {
app.getChosenItemValues()
.then(res => {
const [clothingChoice, groceryChoice, wineChoice, voucherChoice] = res;
// assertions here
done();
});
});
});
Excuse me if the test functions aren't in the correct format, I don't use Mocha or Chai.