why is using .then() 3x faster then await in my test? - javascript

This could totally be me misunderstanding something about the way async/await works in Javascript, but I haven't figured it out yet.
I have a simple test that uses some helpers to generate mock data before actually running the test.
Here's the helper function:
async create(numCustomers, businessId, options) {
options = _.assign({ type: 'customer' }, options);
const customers = await Factory.createMany('user', options, numCustomers);
return Promise.all(customers.map(async (c) => {
await AccountHelper.create(businessId, c.get('id'));
return c;
}));
}
and here's two versions of the test:
async/await version:
const customers = await CustomerHelper.create(10, Constants.fakeBusinessId);
await Promise.all(customers.map(async (c) => {
await PetHelper.create(1, c.get('id'));
}));
const res = await server.inject(request);
expect(res.statusCode).to.equal(200);
.then() version:
CustomerHelper.create(10, Constants.fakeBusinessId).then(async (customers) => {
await Promise.all(customers.map(async (c) => {
await PetHelper.create(1, c.get('id'));
}));
const res = await server.inject(request);
expect(res.statusCode).to.equal(200);
});
The .then() version finishes in about 2 seconds, where the async/await version finishes in almost 7 seconds. Changing between these two forms seems to be the only variable.
I'm running Node 8.9.4 on OSX.
I appreciate any insights or education :)

Both methods should be roughly the same duration, as long as you are properly signaling your test framework that the test is done. (Either by returning a promise or calling the done callback, in most test frameworks)
If you do not signal the framework properly, then the test will exit before the async processing is finished (or even started, in many cases). Also, if your assertions / expectations wait for an asynchronous event, then they will not even be made, and a test with no assertions is (in most frameworks) a passing test. Because of this, async/await is becoming the preferred style for tests, because it is harder (though not impossible) to incorrectly signal the test framework (as your example illustrates).
Proper ways to signal a test framework the test is done (for mocha/jest style frameworks):
it('does async work with await', async function (){
let result = await asyncWork()
assertItWorked(result)
})
it('does async work with returned promise', function (){
return asyncWork().then(function (result) {
assertItWorked(result)
})
})
it('does async work with promise and callback', function (done){
asyncWork().then(function (result) {
assertItWorked(result)
done() // note that this method will timeout if it fails, instead of displaying the error message, but you still get a failure
})
})
it('does async work with promise and chained callback', function (done){
asyncWork().then(function (result) {
assertItWorked(result)
}).then(done)
})
Improper ways that almost look right:
it('does async work but does not wait', function (done){
asyncWork().then(function (result) {
assertItWorked(result)
})
done() // this gets called before async work is done, and no assertions are made
})
it('does async work with promise and chained callback, but calls callback too soon', function (done){
asyncWork().then(function (result) {
assertItWorked(result)
}).then(done()) // this is effectively identical to the previous example, because done gets called immediately
})
also note that the async/await style fully catches thrown errors/rejections from asyncWork, although with more code the other styles could as well.

Related

Does NestJS guarantee to run async methods after return?

What will happen when a code without await an async function returns from a REST API of NestJS?
// modified from https://stackoverflow.com/questions/59829254
#Post()
async create(#Body() body: Dto, #Headers('id') id: string) {
body.id = id;
const item = await this.service.create(body);
this.service.anotherAsyncMethod().then(...).catch(...);
return item;
}
In this example, does the process always live to run the complete anotherAsyncMethod?
If it does, how can it be? Do NestJS or Javascript itself guarantee the way to execute?
It seems there are some waitings in simple test code.
async function asyncFunction() {
setTimeout(() => console.log("after 3s"), 3000);
}
asyncFunction();
console.log('END');
// output
// END
// after 3s
But I could not find the clue that it always work in NestJS scenario and the others.
In JavaScript, any piece of code that comes before the return statement will be executed. Do not forget that the await statement will only wait for the result, and if you put await before an async operation, it will wait for its result, but even if you did not put await, the async operation still placed in the event loop and will still be executed.
The only principle is that all these operations should be before the return statement. The return means termination of that function. Not only in JavaScript but in all languages, nothing will be executed after return.
function someOperations(status) {
return new Promise((res, rej) => {
setTimeout(() => {
console.log(status)
res('done');
}, 3000);
});
}
(async () => {
console.log('start...');
await someOperations('first');
someOperations('second');
console.log('second someOperations skipped for now');
return;
console.log('unreachable'); // will not be executed
})()

Mocha/Chai/Supertest passing tests when not meeting requirements [duplicate]

I'm using Mocha and SuperTest to test my Express API. However my first test always seems to pass when inside the .then() of my request().
I'm passing in a String to a test that is expecting an Array. So should definitely fail the test.
It fails outside of the then() as expected, but I won't have access to the res.body there to perform my tests.
Here is my code:
const expect = require('chai').expect;
const request = require('supertest');
const router = require('../../routes/api/playlist.route');
const app = require('../../app');
describe('Playlist Route', function() {
// before((done) => {
// }
describe('Get all playlists by user', function() {
it('Should error out with "No playlists found" if there are no Playlists', function() {
request(app).get('/api/playlists/all')
.then(res => {
const { body } = res;
// Test passes if expect here
expect('sdfb').to.be.an('array');
})
.catch(err => {
console.log('err: ', err);
});
// Test fails if expect here
expect('sdfb').to.be.an('array');
})
})
});
I found this article but I'm not using a try catch block, but I thought maybe it could have something to do with the promise.
Quick reponse
it('decription', function(done) {
asyncFunc()
.then(() => {
expect(something).to.be(somethingElse)
done()
})
})
Detailed response in the comment of #jonrsharpe
Rather than using done, simply return request(app).get('/api/playlists/all') since request() returns a promise. Since you have expect('sdfb').to.be.an('array'); twice, remove the one that's not in the .then callback. When using asynchronous code, remember that synchronous code that appears to come after the async chain will execute before the promise .then handlers. This is counterintuitive.
Here's the .then approach:
it('should ...', () => {
return request(app)
.get('/api/playlists/all')
.then(res => {
const {body} = res;
// assert here
});
});
The other approach is to await the promise yourself in the test case function, then make assertions on the resolved response object. In this case, drop the then chain. This approach is generally preferred as it reduces nesting.
it('should ...', async () => {
const res = await request(app).get('/api/playlists/all');
const {body} = res;
// assert here
});
If you don't let Mocha know you're working with asynchronous code by returning a promise, awaiting the promises, or adding and calling the done parameter, the assertions occur asynchronously after the test is over and disappear into the void, creating a false positive.
Skip .catch either way. Since you've informed Mocha of the promise, if it rejects, it'll let you know.

Chai: throwing error on async/await when no parameter is passed

I am trying to test my code (Typescript) and it should throw when no parameter is passed
getID(ID) { if(!ID){throw new Error('stop js')} ....}
it('should fail if no ID', async () => {
expect(async () => await myService.getID() ).to.throw("stop js");
})
Based on the documentation the above should work however when I run the test I get
1) myTest
should fail if no groupId is passed:
AssertionError: expected [Function] to throw an error
You are operating on Promises; async/await is just syntactic sugar for Promises as well.
When you run code like this:
it('should fail if no ID', () => {
expect(/* not async */ myService.getID()).to.throw("stop js");
});
...the call to getID will synchronously throw an Error. However, when you run code like this:
it('should fail if no ID', async () => {
expect(async () => await myService.getID()).to.throw("stop js");
});
...the call to async will pass a Promise into expect, which will asynchronously be rejected with your Error.
As mentioned by NineBerry in the comments, you can install and use the library chai-as-promised to operate on Promises:
return expect(async () => await myService.getID()).to.eventually.throw("stop js");
// or
return expect(async () => await myService.getID()).to.eventually.be.rejectedWith("stop js");
You will either need to return the result of expect, or await it; otherwise your test will not wait for the expect result before determining whether it succeeds.

How would I test this promise based code with jest?

How would I test this code in jest? I'd like to make sure that the error and success of the passed promise is being called as needed. I'm sure it's something sorta simple, but it's driving me crazy. Thanks very much.
handleStatusChangeRequest (changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
If your code uses promises, there is a nice way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve.
If the promise is rejected, the test will automatically fail.
For example, let's say that changeData, instead of using a callback, returns a promise that is supposed to resolve to the string "status has been successfully modified".
Be sure to return the promise - if you omit this return statement, your test will complete before your changeData() -[async function] completes.
Here's a convenient and easy to follow pattern
test('if the data is changed', () => {
return changeData().then((data) => {
expect(data).toBe('status has been successfully modified');
});
})
Happy testing :)
This could be refactored, but for the sake of demonstration, I left the repeating bits in.
In example.spec.js, the callback, changeEntryStatus, is stubbed to return a promise. In order to check if other instance methods (this.method) were called, they are first mocked, then assertions are called on the mock after running the method being tested. Learn more in the Jest docs. (See my thoughts on mocking methods of the unit being tested at the bottom.)
Run the example on repl.it.
example.js:
class Example {
handleStatusChangeRequest(changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
handleStatusChangeSuccess() {
console.log('stubbed handleStatusChangeSuccess')
}
handleErrorDisplay(error) {
console.log('stubbed handleErrorDisplay:', error)
}
}
module.exports = Example;
example.spec.js:
const Example = require('./entryStatus')
describe('handleStatusChangeRequest', () => {
it('should run the changeEntryStatus callback', () => {
const {handleStatusChangeRequest} = new Example()
const stub = jest.fn().mockResolvedValue()
handleStatusChangeRequest(stub)
// must return because handleStatusChangeRequest is asynchronous
return expect(stub).toHaveBeenCalled()
});
it('should call example.handleStatusChangeSuccess', async () => {
const example = new Example()
const stub = jest.fn().mockResolvedValue()
example.handleStatusChangeSuccess = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleStatusChangeSuccess).toHaveBeenCalled();
})
it('should call example.handleErrorDisplay', async () => {
const example = new Example()
const fakeError = { code: 'fake_error_code' }
const stub = jest.fn().mockRejectedValue(fakeError)
example.handleErrorDisplay = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleErrorDisplay).toHaveBeenCalled()
expect(example.handleErrorDisplay).toHaveBeenCalledWith(fakeError)
});
});
Opinionated Disclaimer: Mocking methods of the unit under test is a smell. Consider checking for the expected effects of calling handleStatusChangeSuccess and handleErrorDisplay instead of checking to see if they were called. Then don't even expose those methods publicly unless consumers of the class need access.
Opinionated Disclaimer: Mocking methods of the unit under test is a
smell. Consider checking for the expected effects of calling
handleStatusChangeSuccess and handleErrorDisplay instead of checking
to see if they were called. Then don't even expose those methods
publicly unless consumers of the class need access.
I wholeheartedly agree with webprojohn's disclaimer. Mocks are a smell as tests should assert the behavior of the code, not its implementation. Testing the latter makes the code brittle to change.
Stepping off my soapbox... :) We're looking for a way to test an asynchronous method. I'm not sure what assertions your tests should make to verify the behavior inside handleStatusChangeSuccess() and handleErrorDisplay(err) so the example below leaves a comment where those assertions would go. The following uses Promise.resolve() and Promise.reject() to trigger the outcomes to test. I've used async/await, Jest has other async examples in their docs.
const Example = require('./example')
describe('handleStatusChangeRequest', () => {
it('should resolve successfully', async () => {
const {handleStatusChangeRequest} = new Example();
const resolvePromise = () => Promise.resolve();
await handleStatusChangeRequest(resolvePromise);
// resolution assertions here
});
it('should resolve errors', async () => {
const {handleStatusChangeRequest} = new Example();
const fakeError = new Error('eep');
const rejectPromise = () => Promise.reject(fakeError);
// if your method doesn't throw, we can remove this try/catch
// block and the fail() polyfill
try {
await example.handleStatusChangeRequest(rejectPromise);
// if we don't throw our test shouldn't get here, so we
// polyfill a fail() method since Jest doesn't give us one.
// See https://github.com/facebook/jest/issues/2129
expect(true).toBe(false);
}
catch (e) {
// rejection assertions here
}
});
});
The answer I have looks so:
**Success tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleStatusChangeSuccess')
await instance.handleStatusChangeRequest(() => Promise.resolve('cool man'))
expect(spy).toHaveBeenCalledTimes(1)
**Error tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleErrorDisplay')
await instance.handleStatusChangeRequest(() => Promise.reject(Error('shit')))
expect(spy).toHaveBeenCalledTimes(1)
As I stated above, the handleStatusChangeSuccess and handleError methods are test else where with some snapshots (they just set state and render out some different jsx). I feel pretty good about this. I'm using spys/mocks, but I'm testing the implementation functions elsewhere. Sufficient?

Can't test effects of resolved promise

I have the following code in user:
import { redirectTo } from 'urlUtils';
export function create(user) {
api.postUser(user).then((response) => {
redirectTo(response.userUrl);
})
}
And I have the following test:
import * as api from 'api'
import * as user from 'user'
sinon.stub(api, 'postUser').returns(
Promise.resolve({ userUrl: 'a-url' })
);
sinon.spy(urlUtils, 'redirectTo');
const userData = {id: 2};
user.create(userData);
expect(api.postUser.calledWith(userData)).toBe(true); // This passes
expect(urlUtils.redirectTo.calledOnce).toBe(true); // This fails
I have been able to test it on the browser, and the redirect is happening. What am I missing here? I have stubbed the request call to resolve the promise synchronously, so that shouldn't be a problem.
Promises are asynchronous, so when you're doing expect(urlUtils.redirectTo.calledOnce).toBe(true) the promise hasn't been resolved yet.
The easiest way to get around it is to wrap that expectation in a short timeout and then use whatever utility the testing framework you're using provides to signal that an asynchronous test is complete. Something like this:
setTimeout(() => {
expect(urlUtils.redirectTo.calledOnce).toBe(true);
done();
}, 5);
Another, nicer solution is to actually use the promise that your stub returns. First, keep a reference to that promise:
Replace:
sinon.stub(api, 'postUser').returns(
Promise.resolve({ userUrl: 'a-url' })
);
with:
const postUserPromise = Promise.resolve({ userUrl: 'a-url' });
sinon.stub(api, 'postUser').returns(postUserPromise);
then write your expectation like this:
postUserPromise.then(() => {
expect(urlUtils.redirectTo.calledOnce).toBe(true);
done();
});
done() is the function most test frameworks (at least Jasmine and Mocha as far as I know) provide to signal that an asynchronous test is completed. You get it as the first argument to the function your test is defined in and by specifying it in your function signature you're telling the test framework that your test is asynchronous.
Examples:
it("is a synchronous test, completed when test function returns", () => {
expect(true).to.equal(true);
});
it("is an asynchronous test, done() must be called for it to complete", (done) => {
setTimeout(() => {
expect(true).to.equal(true);
done();
}, 5000);
});

Categories

Resources