Mocha Dynamic test generation in before block not getting executed - javascript

As suggested in this post , I tried the steps to create dynamic tests , but I see the actual test(test.getMochaTest()in my below implementation) not getting executed. What's that I'm missing here, the call on test.getMochaTest() does not get executed in the before block.
describe('Dynamic Tests for Mocha', async () => {
let outcome ;
before(async() => {
await init().catch(() => console.error('Puppeteer environment initialization failed'));
return collectTests().then(async(collectTests) => {
console.info('4.Executing tests :');
describe('Dynamic test execution', async() => {
collectTests.forEach(async(test) => {
console.info(`\tModule under test : ${test.name}`);
// impl. of test.getMochaTest() DOES NOT get executed.
it(test.name, async() => outcome = await test.getMochaTest().catch(async () => {
console.error(`error while executing test:\t${test.name}`);
}));
});
}) ;
});
});
after(async () => {
console.info('5. Exiting tests...');
await HelperUtils.delay(10000).then(async () => { await browser.close(); });
console.log('executing after block');
});
it('placeholder', async() => {
await
console.log('place holder hack - skip it');
});
});
Array of tests is returned here :
async function collectTests():Promise<Array<ITest>> {
console.info('3.Collecting tests to execute ...');
testArray = new Array<ITest>();
const login:ITest = new SLogin('Login Check');
testArray.push(login);
return testArray;
}
The below implementation of getMochaTest in SLogin -> does not get executed .
export default class SLogin extends BTest implements ITest {
constructor(name: string) {
super(name);
}
async getMochaTest():Promise<Mocha.Func> {
return async () => {
console.log('Running Login check');
expect(true).to.equal(true);
};
}
}

It doesn't look like you're actually invoking the test.
Calling test.getMochaTest() only returns the async test function in a Promise, it doesn't execute it. So your catch block is catching errors while obtaining the function, not while executing it.
Breaking it out across multiple lines will hopefully make things clearer.
Here's what your code sample does. Notice it never executes the returned test function:
it(test.name, async () => {
const testFn = await test.getMochaTest().catch(() =>
console.error(`error while ***obtaining*** test: \t${test.name}`));
// oops - testFn never gets called!
});
And here's a corrected version where the test actually gets called:
it(test.name, async () => {
const testFn = await test.getMochaTest().catch(() =>
console.error(`error while ***obtaining*** test: \t${test.name}`));
const outcome = await testFn().catch(() =>
console.error(`error while ***executing*** test: \t${test.name}`));
});
Note: I wrote it that way with await and catch() to better match the format of your code sample. However, it's worth pointing out that it mixes async/await and Promise syntax. More idiomatic would be to catch errors with a try/catch block when using async/await.

Related

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

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

Test failing after adding then and catch to promise

I have the following (simplified for the sake of scoping down problem) code:
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises(async (c) => {
const results = await Promise.all(pushPromises())
c.success(results);
});
My test mocks those firstPromise and secondPromise to check if they were called with the right arguments. This works (assume mock set up is properly done):
jest.mock('src/firstPromise');
jest.mock('src/secondPromise');
describe('test', () => {
let firstMock;
let secondMock;
beforeEach(() => {
require('src/firstPromise').default = firstMock;
require('src/secondPromise').default = secondMock;
})
it('test', async () => {
await handlePromises(context);
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
});
Now, if I add handlers to the promises such as:
function pushPromises() {
const promises = [];
promises.push(
firstPromise('something')
.then(r => console.log(r))
.catch(e => console.log(e))
)
promises.push(
secondPromise('somethingelse')
.then(r => console.log(r))
.catch(e => console.log(e))
)
return promises;
}
The first expectation passes, but the second one doesn't. It looks like the second promise is no longer called.
How can I modify the test/code so that adding handlers on the promises don't make the test break? It looks like it is just finishing the execution on the first promise handler and does never get to the second one.
EDIT:
I have modified the function to return a Promise.all without await:
export default handlePromises(async (c) => {
return Promise.all(pushPromises())
});
But I'm having the exact same issue. The second promise is not called if the first one has a .then.
In your edit, your handlePromises is still not a promise..
Try the following. ->
it('test', async () => {
await Promise.all(pushPromises());
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
Your handlePromises function accepts a callback but you are handling it as it returns a promise, it is not a good way to go but you can test your method using callback as following
Modify your Test as=>
it('test', (done) => {
handlePromises(context);
context.success = (results) => {
console.log(results)
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
done();
}
});
Or modify your code as =>
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises = (context) => {
return Promise.all(pushPromises()).then((data) => context.success(data))
};
//with async
export default handlePromises = async (context) => {
let data = await Promise.all(pushPromises());
context.success(data)
};

Stubbing function to return something acceptable for .promise()

I'm running tests and I'm stubbing a function that calls the AWS sqs.deleteMessage function. .promise() is called on the call to this function. Every time I run my tests with the coverage I notice that it jumps to the catch block thus an error must be occurring on my .promise() call.
I've tried stubbing the function to resolve the promise but that doesn't seem to work. I've tried returning data as well and still have the same issue.
Below is an example of the code I'm trying to test. It never reaches the logger.info() line
fooObj.js
const foo = async (req) => {
try{
let res = await bar.deleteMessage(handle).promise();
logger.info("Sqs result message " + JSON.stringify(res));
} catch(error){
#catch block code
}
}
Below is the code for bar.deleteMessage()
bar.js
const aws = require('aws-sdk');
const sqs = new aws.SQS();
deleteMessage = function(handle){
return sqs.deleteMessage({
ReceiptHandle: handle
});
}
And finally here is the test code
const fooObj = require('foo')
const barObj = require('bar')
jest.mock('bar')
describe('foo test', ()=>{
test('a test' , ()=>{
barObj.deleteMessage.mockImplementation(()=>{
return Promise.resolve({status:200})
});
return fooObj.foo(req).then(data=>{
#Expect statements here
})
}
}
So I would like the logger.info line to be reached in coverage but I assume the issue has to do with how I'm stubbing the bar.deleteMessage function. I could use the aws-sdk-mock but I feel like I'm violating unit testing principles by mocking the sqs call that is in another file and the proper way to do it would simply be to properly stub the bar.deletemessage() function
You just need one change:
bar.deleteMessage needs to return an object with a promise property set to the function that returns the Promise:
barObj.deleteMessage.mockImplementation(() => ({
promise: () => Promise.resolve({ status: 200 })
}));
...or you can shorten it to this if you want:
barObj.deleteMessage.mockReturnValue({
promise: () => Promise.resolve({ status: 200 })
});

Jest cleanup after test case failed

What is a good and clean way to cleanup after a test case failed?
For a lot of test cases, I first create a clean database environment, which needs to be cleaned up after a test case finishes.
test('some test', async () => {
const { sheetService, cleanup } = await makeUniqueTestSheetService();
// do tests with expect()
await cleanup();
});
Problem is: If one of the expects fails, cleanup() is not invoked and thus the database environment is not cleaned up and jest complains Jest did not exit one second after the test run has completed. because the connection is not closed.
My current workaround looks like this, but it doesn't feel good and clean to push the cleanup hooks to an array which than is handled in the afterAll event.
const cleanUpHooks: (() => Promise<void>)[] = [];
afterAll(async () => {
await Promise.all(cleanUpHooks.map(hook => hook()));
});
test('some test', async () => {
const { sheetService, cleanup } = await makeUniqueTestSheetService();
// do tests with expect()
await cleanup();
});
Use try/finally block for such cases.
For example:
it("some test case", async done => {
try {
expect(false).toEqual(true);
console.log("abc");
done();
}finally{
console.log("finally");
// cleanup code below...
}
});
Above code would just execute the finally block (hence "finally" gets printed in console instead of "abc".
Note that the catch block gives a timeout so just use finally.
This answer must be useful.
What if you use afterEach()? it will execute after each test
test('some test', async () => {
const { sheetService, cleanup } = await makeUniqueTestSheetService();
// do tests with expect()
});
afterEach(async()=>{
await cleanup();
});

Jest - checking local storage is called in an async function that is being mocked

I have an api call in a react component that looks like this.
login = () => {
// <--- If I set the localStorage on this line the test passes.
apiRequest.then(res => {
localStorage.setItem('token', res.token);
});
}
To test it I have mocked the api call. I want to check that the local storage is called, so have also mocked localStorage, however, as the localStorage is set in the mocked api call it never gets called. My test code is below. Does anyone know how I can check that the local storage is set in a mocked call. I have confirmed that if I move the localStorage outside the apiRequest it works, so it is being mocked correctly, the issue is definitely that it is in the apiRequest.
// This mocks out the api call
jest.mock('./api', () => {
return {
apiRequest: jest.fn(
() =>
new Promise(resolve => {
resolve();
})
),
};
});
const localStorageMock = (() => {
const store = {};
return {
setItem: jest.fn((key, value) => {
store[key] = value.toString();
})
}
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
it('sets a token in local storage', () => {
const { getByText } = render(<Login />);
const loginButton = getByText(/login/i);
// This passes
expect(apiRequest).toBeCalledTimes(1);
// This never gets called as it is being called in the apiRequest
expect(localStorage.setItem).toBeCalledWith('token', '1234');
});
If anything is unclear let me know and I will provide more details.
localStorage.setItem is called in async way through .then
login = () => {
apiRequest.then(res => {
localStorage.setItem('token', res.token);
});
}
So mocking has nothing to help with async flow. This small part
.then(res => {
localStorage.setItem('token', res.token);
}
is just put into the end of queue(it's named microtask queue if you are interested in details)
So your test code is finished and only after that this small microtask is executed.
How could you handle that? You can write test in async way and put additional expect into dedicated microtask that will run after those with localStorage.setItem call.
You can use setTimeout(macrotask) for this:
it('sets a token in local storage', done => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
expect(apiRequest).toBeCalledTimes(1);
setTimeout(() => {
// runs after then(....setItem) has been called
expect(localStorage.setItem).toBeCalledWith('token');
done();
}, 0);
});
or create microtask with Promise/async/await:
it('sets a token in local storage', async () => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
expect(apiRequest).toBeCalledTimes(1);
await Promise.resolve(); // everything below goes into separate microtask
expect(localStorage.setItem).toBeCalledWith('token');
});
[UPD] interesting thing about await that it can be used with everything else not only Promise. And it could work like Promise.resolve(<some value here>). So in your case
it('sets a token in local storage', async () => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
await expect(apiRequest).toBeCalledTimes(1);
expect(localStorage.setItem).toBeCalledWith('token');
});
will work as well. But I believe it looks confusing("waaaat? does .toHaveBeenCalled() return Promise for real?!") and suspicious(it's a magic! I'm not allowed to touch that!). So it's better to choose some version with straightforward "deferring"
A common problem when you try to test async code, is that you also need async tests, try to await to the apiRequest to be resolved and then verify if the local storage was called.

Categories

Resources