Why does the test expressed with eventually pass? I'm obviously missing something in the syntax or use case.
const chai = require('chai')
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const {expect} = chai
function p() {
return new Promise( (resolve) => {
setTimeout( () => {
resolve(10);
},5000);
})
}
describe('Chai Eventually test', () => {
it('should fail!', () => {
expect(p()).to.eventually.equal(5)
});
it('should also fail!', async () => {
expect(await p()).to.equal(5)
});
})
running it with mocha I get:
Chai Eventually test
✔ should fail!
1) should also fail!
1 passing (2s)
1 failing
1) Chai Eventually test
should also fail!:
AssertionError: expected 10 to equal 5
+ expected - actual
-10
+5
at Context.<anonymous> (chaiError.js:19:26)
1 passing (4ms)
This happens immediately so it is not waiting for the promise to resolve.
You have a couple options here.
The simplest is to just return the expect:
it('should fail!', () => {
return expect(p()).to.eventually.equal(5); // <-- return this expectation
});
Alternatively, you can skip the return and use a done callback passed into chai's .notify method:
it('should fail using done!', (done) => { // <-- pass done into spec function
expect(p()).to.eventually.equal(5).notify(done); // <-- pass done to .notify()
});
Documentation here.
And here's a StackBlitz showing it working correctly (i.e., failing) with those changes.
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 write a test using JEST. I do not know how to test promise recursion in JEST.
In this test, the retry function that performs recursion is the target of the test until the promise is resolved.
export function retry<T>(fn: () => Promise<T>, limit: number = 5, interval: number = 1000): Promise<T> {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
setTimeout(() => {
// Reject if the upper limit number of retries is exceeded
if (limit === 1) {
reject(error);
return;
}
// Performs recursive processing of callbacks for which the upper limit number of retries has not been completed
try {
resolve(retry(fn, limit - 1, interval));
} catch (err) {
reject(err);
}
}, interval);
});
});
}
Perform the following test on the above retry function.
retry() is resolved in the third run. The first, second and third times are called every 1000 seconds respectively.
I thought it would be as follows when writing these in JEST.
jest.useFakeTimers();
describe('retry', () => {
// Timer initialization for each test
beforeEach(() => {
jest.clearAllTimers();
});
// Initialize timer after all tests
afterEach(() => {
jest.clearAllTimers();
});
test('resolve on the third call', async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error('Async error'))
.mockRejectedValueOnce(new Error('Async error'))
.mockResolvedValueOnce('resolve');
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
await retry(fn);
// Advance Timer for 1000 ms and execute for the second time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(2);
// Advance Timer for 1000 ms and execute for the third time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(3);
await expect(fn).resolves.toBe('resolve');
});
});
As a result, it failed in the following error.
● retry › resolve on the third call
Timeout - Async callback was not invoked within the 30000ms timeout specified by jest.setTimeout.Error:
> 16 | test('resolve on the third call', async () => {
| ^
17 | jest.useFakeTimers();
18 | const fn = jest
19 | .fn()
I think that it will be manageable in the setting of JEST regarding this error. However, fundamentally, I do not know how to test promise recursive processing in JEST.
Your function is so hard to testing with timer.
When you call await retry(fn); this mean you will wait until retry return a value, but the setTimeout has been blocked until you call jest.advanceTimersByTime(1000); => this is main reason, because jest.advanceTimersByTime(1000); never been called.
You can see my example, it is working fine with jest's fake timers.
test("timing", async () => {
async function simpleTimer(callback) {
await callback();
setTimeout(() => {
simpleTimer(callback);
}, 1000);
}
const callback = jest.fn();
await simpleTimer(callback); // it does not block any things
for (let i = 0; i < 8; i++) {
jest.advanceTimersByTime(1000); // then, this line will be execute
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
expect(callback).toHaveBeenCalledTimes(9); // SUCCESS
});
I think, you could skip test timer detail, just test about you logic: fn has been called 3 times, finally it return "resolve"
test("resolve on the third call", async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error("Async error"))
.mockRejectedValueOnce(new Error("Async error"))
.mockResolvedValueOnce("resolve");
// expect.assertions(3);
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
const result = await retry(fn);
expect(result).toEqual("resolve");
expect(fn).toHaveBeenCalledTimes(3);
});
Note: remove all your fake timers - jest.useFakeTimers
The documentation of jest suggests that testing promises is fairly straightforward ( https://jestjs.io/docs/en/asynchronous )
They give this example (assume fetchData is returning a promise just like your retry function)
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
I agree with #hoangdv this recursive setTimeout pattern is very tricky to test. This is the only solution that worked for me after trying #hoangdv's (which gave me much inspiration).
Note I'm using a similar retry pattern around network Fetches. I'm using React Testing Library to look for an error message that only appears when the retry function ran 3 times.
it('Shows file upload error on timeout', async () => {
jest.useFakeTimers();
setupTest();
// sets up various conditions that trigger the underlying fetch
while (
screen.queryByText(
'There appears to be a network issue. Please try again in a few minutes.'
) === null
) {
jest.runOnlyPendingTimers(); // then, this line will be execute
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
await waitFor(() => {
expect(
screen.getByText(
'There appears to be a network issue. Please try again in a few minutes.'
)
).toBeInTheDocument();
});
jest.useRealTimers();
});
I have the following test in my react native application, but the test should fail (because the action returned is not equal to the action I put in expectedActions. My guess is that it is passing because the expect test runs after the test has completed.
How can I force the test to wait until the promise is completed and the expect test runs? Is there another way of doing this?
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});
Ok, looks like I just missed some of the Jest docs - if you return the promise, i.e. return auth.authorize.mockImplementation(() => Promise.resolve... then Jest will wait until it's completed before continuing.
The are varios ways to test async code. Check the docs for examples: https://facebook.github.io/jest/docs/en/asynchronous.html
One could be returning the promise:
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
return authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});
I'm having trouble getting an async event emitter test running in mocha v5.0.4. I'm using a typed Rx based event emitter and node.js v8.10. Can anyone point me in the right direction please?
The first and the second it-blocks work fine. However, the third it-block is never invoked. Mocha doesn't give me a test result (no output at all, mocha runs into the timeout which I set to 10 minutes)
describe('xyz', () => {
const xyz : Xyz = new Xyz(config);
describe('make()', () => {
let emitter : Emitter<Status>;
emitter = xyz.make("abc", 0.01);
it('should return an Emitter', () => {
expect(emitter).to.be.a('Object');
});
it('should eventually return a foo', (done) => {
emitter.on('foo').subscribe( foo => {
expect(foo).to.be.a('foo');
done();
})
});
it('should eventually return a bar', (done) => {
emitter.on('bar').subscribe( bar => {
console.log('foobar!');
expect(bar).to.be.a('bar');
done();
});
});
});
});
What strikes me most is that the event is definitely fired. I can see foobar! as the console output. I'm not using Spies but sticking to the second example from the mocha documents.
As part of my redux action, it makes several sequential api requests. The apiCall method returns a Promise with some value, and that value is used by a subsequent apiCall to make another request, and so on. I'm using Jest to test these api calls.
const myAPI = {
apiCall(param: any): Promise<any> {
return new Promise((resolve, reject) => {
resolve('result');
});
},
};
const serialAPIRequests = () => {
myAPI.apiCall('first_param')
.then((result) => {
console.log(result);
return myAPI.apiCall(result);
})
.then((result) => {
console.log(result);
return myAPI.apiCall(result);
})
.then((result) => {
console.log(result);
return Promise.resolve(result);
});
};
I am trying to write a test to ensure apiCall has been called the correct number of times and with the right parameters.
describe.only('my test', () => {
it('should test api stuff', () => {
myAPI.apiCall = jest.fn()
.mockReturnValueOnce(Promise.resolve('result1'))
.mockReturnValueOnce(Promise.resolve('result2'))
.mockReturnValueOnce(Promise.resolve('result3'));
serialAPIRequests();
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
});
});
What happens is that Jest report Expected mock function to have been called three times, but it was called one time.
Jest test result shows that
● Console
console.log
result1
console.log
result2
console.log
result3
● my test › should test api stuff
expect(jest.fn()).toHaveBeenCalledTimes(3)
Expected mock function to have been called three times, but it was called one time.
The fact that console.log showed different values means the mocked return was properly passed through the mock function and it was called 3 times.
What could be causing this and how do I properly test this function?
Use async/await to test async code. Read more here: https://facebook.github.io/jest/docs/en/tutorial-async.html
describe.only('my test', () => {
it('should test api stuff', async () => {
myAPI.apiCall = jest.fn()
.mockReturnValueOnce(Promise.resolve('result1'))
.mockReturnValueOnce(Promise.resolve('result2'))
.mockReturnValueOnce(Promise.resolve('result3'));
await serialAPIRequests();
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
});
});
Promises are async so by the time you do you check the mock was actually called once.
You could do this instead. Wait for all calls to be done and return a promise to indicate the test is async.
return serialAPIRequests().then(() => {
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
})