Node: how to test multiple events generated by an async process - javascript

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)
});
});

Related

Async await test cases failed using jest javascript testing library

I am using Jest testing Library for some simple async/await functions. But it's failing again and again as I am very new to jest. and can you please answer what expect.assertions(1) do here
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1,
name: "test",
age: 20,
});
}, 1000);
});
}
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
test("async await error", async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch("error");
}
});
As I pointed out in the comments, the test for the "failure" case doesn't really make sense if this fetchData is the real function because it never 'rejects'. To test the failure case in Jest, you'd need to somehow trigger the Promise.reject case.
If we assume this fetchData is a wrapper on an api call or something else, we could imagine something like this.
You might have a library or module that is making api calls like:
// api.js
const api = {
actualFetchData: () => {
// this is the function that actually connects
// to a data source and returns data
},
};
module.exports = api;
And your fetchData function which you're trying to test looks like:
// fetchData.js
const api = require("./api");
function fetchData() {
return api.actualFetchData();
}
module.exports = fetchData;
Then, assuming this structure matches what you're working on, you can mock the internals of fetchData and test both success and failure cases by mocking actualFetchData and using mockResolvedValue and mockRejectedValue.
// fetchData.test.js
const fetchData = require("./fetchData");
const api = require("./api");
jest.mock("./api");
const mockApiFetch = jest.fn();
api.actualFetchData = mockApiFetch;
describe("when the underlying fetch resolves", () => {
beforeEach(() => {
mockApiFetch.mockResolvedValue({
id: 1,
name: "test",
age: 20,
});
});
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
});
describe("when the underlying fetch fails", () => {
beforeEach(() => {
mockApiFetch.mockRejectedValue(new Error("failed to get data"));
});
test("async await error", async () => {
expect(() => fetchData()).rejects.toThrow("failed to get data");
});
});
You'll notice I didn't use the expect.assertions because it didn't seem like it added anything to the test. Instead, just used toThrow with text that matches the error.
I realize this is making some assumptions about a system that you haven't fully described in the initial question so this may not be exactly what you're trying to get at. Hopefully it's close.

Mocha.run() exits without awaiting async tests

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);
}
});

Jest: shared async code between test blocks

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)
...
}
...
})

Test callback function with jest

I'm trying to test a function with a callback inside. I set up a mock function, but I also need to test a callback.
I've tried to separate it as another mock function, but it doesn't counted as covered.
Function I'm trying to test:
export const checkDescription = async page => {
const metaDescription = await page.$eval(
'meta[name="description"]',
description => description.getAttribute("content")
);
return metaDescription;
};
I've mocked the page function :
const page = {
$eval: jest.fn(() => "Value")
};
my test :
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value");
expect(page.$eval).toHaveBeenCalled();
});
I've tried to separate description :
const description = {
getAttribute: jest.fn(() => "Value")
};
but I don't think that it's a correct way to cover description inside $eval.
You're close!
The description arrow function is passed to your page.$eval mock function so you can use mockFn.mock.calls to retrieve it.
Once you've retrieved it, you can call it directly to test it and get full code coverage:
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value"); // Success!
expect(page.$eval).toHaveBeenCalled(); // Success!
const description = page.$eval.mock.calls[0][1]; // <= get the description arrow function
const getAttributeMock = jest.fn(() => 'mock content');
expect(description({ getAttribute: getAttributeMock })).toBe('mock content'); // Success!
expect(getAttributeMock).toHaveBeenCalledWith('content'); // Success!
// Success! checkDescription now has full code coverage
});
I receive async messages from serial port via callbacks. Try to read here:
https://jest-bot.github.io/jest/docs/asynchronous.html
import { InpassTerminal } from "../src/main.js"
jest.setTimeout(45000);
describe('Basic tests', () => {
test('1. Host connection', async (done) => {
await new Promise( resolve => setTimeout(resolve, 500) );
const commandTest = {actionCode: '12345', terminalId: '1019****'}
function cb (data) {
if (data.operationCode == 12345) {
const actualStatus = Buffer.from(data.status, "ascii")
const expectedStatus = '1'
expect(actualStatus.toString()).toBe(expectedStatus)
done()
}
}
const terminal = new InpasTerminal()
terminal.exec('/dev/ttyPos0', commandTest, cb)
})

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"}]');
});

Categories

Resources