Get sinon to resolve a promise when stubbed function is called - javascript

I have a set of integration tests for message queueing code (over a real RabbitMQ). The tests are verifying that the correct use case is triggered when a specific message arrives.
it("should trigger the use case", () => {
const stub = sinon.stub(app, 'useCaseToTrigger').resolves();
await publishMessage({ id: "SOME_ID" })
// FAIL - Message isn't processed yet.
stub.should.have.been.calledWith(match({id: "SOME_ID" }))
})
This doesn't work. The await statement waits for the message to be published, but says nothing of when the message is processed.
I can add a delay, but that quickly becomes a huge waste of time (if there are multiple tests).
it("should trigger the use case", () => {
const stub = sinon.stub(app, 'useCaseToTrigger').resolves();
await publishMessage({ id: "SOME_ID" })
await new Promise(r => setTimeout(r, 100));
// PASS - the message has been processed before the verification
stub.should.have.been.calledWith(match({id: "SOME_ID" }))
})
But if I make the stubbed function resolve a promise when called, I can get my test to work without any undue delay.
it("should trigger the use case", () => {
const promise = new Promise(r => {
sinon.stub(app, "useCaseToTrigger").callsFake(() => {
setImmediate(() => { r(); }) // Push to the end of the event loop
return Promise.resolve();
})
})
await publishMessage({ id: "SOME_ID" })
await promise;
// PASS - Message processed before continuing test
app.useCaseToTrigger.should.have.been.calledWith(match({id: "SOME_ID" }))
})
This does become a bit cumbersome to deal with (and probably not the easiest to read either). But it also gives eslint floating promises warnings. So to avoid those warnings, I need to rewrite it to:
it("should trigger the use case", () => {
await Promise.all([
new Promise(r => {
sinon.stub(app, "useCaseToTrigger").callsFake(() => {
setImmediate(() => { r(); }) // Push to the end of the event loop
return Promise.resolve();
})
}),
publishMessage({ id: "SOME_ID" })
]);
// PASS - Message processed before continuing test
app.useCaseToTrigger.should.have.been.calledWith(match({id: "SOME_ID" }))
})
Is there a way to make this cleaner.

I think you can use an empty promise to await, so the app logic eventually resolves that promise when it is called. Sinon provides a promise fake that makes this a bit easier than creating a new Promise with an external reference to the resolve function.
const p = sinon.promise()
const stub = sinon.stub(app, 'useCaseToTrigger').callsFake(p.resolve)
await publishMessage({ id: "SOME_ID" })
await p
sinon.assert.calledWith(stub, { id: "SOME_ID" })
It looks like sinon .resolves() uses Promise.resolve at the time of the stub call, so it doesn't have a reference to the eventual promise at stub creation, otherwise a stub.promise type of reference might have been useful here to avoid the additional promise setup.

Related

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.

Does client-side code that calls an an async function need to use await?

Imagine the following hypothetical, minimal implementation of a function that does a simple HTTP GET request using axios. This uses await/async as depicted in the post.
const axios = require('axios')
exports.getStatus = async id => {
const resp = await axios.get(
`https://api.example.com/status/${id}`
)
return resp
}
Is the promise not resolved using await? Is it required that the client uses await as depicted below? Is it safe to assume that anytime a client consumes an async function, that it also needs to use await when calling the function?
// client
const { getStatus } = require('./status')
const response = await getStatus(4)
Short answer, no.
Labelling a function async means 2 things:
1) you're allowed to use await inside the function.
2) The function returns a promise; i.e. you can use await on its return value, or you can use .then, or Promise.all, or even ignore the return value, or anything else you can do with promises.
E.g. you could do the following which (depending on your use case) could be more performant because it needs not wait for the response to continue.
// client
const { getStatus } = require('./status')
const response4 = getStatus(4);
const response5 = getStatus(5);
// do stuff that don't rely on the responses.
response4.then(response => myOutput.setFirstOutput(response));
response5.then(response => myOutput.setSecondOutput(response));
Btw, your first snippet is redundant with its usage of await; it's equivalent to
const axios = require('axios')
exports.getStatus = id =>
axios.get(`https://api.example.com/status/${id}`);
This is because return await promise is equivalent to return promise, both return the same promise. There is one exception regarding rejected promises. Awaiting a rejected promise will throw an exception (which you can catch with a try/catch block), whereas directly returning a rejected promise will not throw an exception but you should still handle the rejection (with a .catch clause). To illustrate:
let promise = new Promise(resolve => setTimeout(() => resolve('resolved after 2 seconds'), 2000));
let returnPromise = () => promise;
let returnAwaitPromise = async () => await promise;
returnPromise().then(value => console.log('returnPromise,', value));
returnAwaitPromise().then(value => console.log('returnAwaitPromise,', value));
No, you don't need to use await. An async function returns a Promise that should be resolved. Instead of using await, you can simply call .then() like so:
// client
const { getStatus } = require('./status');
getStatus(4).then((response) => {
//Do something with the response
}).catch((error) => {
//Handle possible exceptions
});
Below I'll address your question from the comments. The getStatus function could be rewritten like this:
exports.getStatus = (id) => {
return new Promise((resolve, reject) => {
axios.get(`https://api.example.com/status/${id}`).then((resp) => {
resolve(resp);
}).catch((error) => {
reject(error);
})
});
}
It would act exactly the same as in your version. When you call asynchronous functions in series, you have to resolve each of the returned Promises. In your case, there are two of them - the one returned by axios.get and the one returned by getStatus.
No you don’t have to. The async function executes and waits until it returns error or completes execution and return a response!

Jest mock timers not working as expected asynchronously; how to make this test pass?

I am trying to test a queuing component that makes calls and handles a lot of scheduling. I want to test it with a mock api where the api responses are delayed as they would be in real life, but I want to use mock timers and fake the passage of time. In the following bare-bones example, the object under test is the Caller object.
function mockCall(): Promise<string> {
return new Promise<string>(resolve => setTimeout(() => resolve("success"), 20));
}
const callReceiver = jest.fn((result: string) => { console.log(result)});
class Caller {
constructor(call: () => Promise<string>,
receiver: (result: string) => void) {
call().then(receiver);
}
}
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
expect(callReceiver).toHaveBeenCalled();
});
I would think this test should pass, but instead the expect is evaluated before the timer is advanced, so the test fails. How can I write this test so it will pass?
By the way, this test does pass if I use real timers and delay the expect for more than 20 milliseconds, but I am specifically interested in using fake timers and advancing time with code, not waiting for real time to elapse.
The reason is mockCall still returns Promise, even after you mocked timer. So call().then() will be executed as next microtask. To advance execution you can wrap your expect in microtask too:
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
return Promise.resolve().then(() => {
expect(callReceiver).toHaveBeenCalled()
});
});
Beware of returning this Promise so jest would wait until it's done. To me using async/await it would look even better:
it("advances mock timers correctly", async () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
await Promise.resolve();
expect(callReceiver).toHaveBeenCalled();
});
Btw the same thing each time you mock something that is returning Promise(e.g. fetch) - you will need to advance microtasks queue as well as you do with fake timers.
More on microtasks/macrotasks queue: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
Jest repo has open proposal on handling pending Promises in more clear way https://github.com/facebook/jest/issues/2157 but no ETA so far.
You can make the test work by returning the promise to jest as otherwise the execution of your test method is already finished and does not wait for the promise to be fulfilled.
function mockCall() {
return new Promise(resolve => setTimeout(() => resolve('success'), 20));
}
const callReceiver = jest.fn((result) => { console.log(result); });
class Caller {
constructor(callee, receiver) {
this.callee = callee;
this.receiver = receiver;
}
execute() {
return this.callee().then(this.receiver);
}
}
describe('my test suite', () => {
it('advances mock timers correctly', () => {
jest.useFakeTimers();
const caller = new Caller(mockCall, callReceiver);
const promise = caller.execute();
jest.advanceTimersByTime(50);
return promise.then(() => {
expect(callReceiver).toHaveBeenCalled();
});
});
});

How to test then function of Promise.all with spyOn

I'm a newbie to coding, so please ask if more information is necessary.
I want to test a then-block inside a Promise.all with spyOn, but the function get never called.
public foo(): void {
const names = this.getNames();
Promise.all(
names.map(name =>
this.nameService.doSomething( //some params )
)
)
.then(result => this.controller.ok(names))
.catch(error => {
//do something
});
}
This is the test
it('should call controller.ok when name is set', () => {
spyOn(nameService, 'doSomething').and.returnValue(Promise.resolve());
spyOn(controller, 'ok');
service.foo();
expect(nameService.doSomething).toHaveBeenCalledWith({
//some params
});
expect(controller.ok).toHaveBeenCalled(); //fails because never called
});
I've debugged the code and the doSomething get called even with the right params, the code also reaches the then-block.
But the test says, it never gets called, so somewhere in there the code breaks and I don't know why?
The catch-block is not called.
Promises representing the eventual completion or failure of an asynchronous operation. Within your test, when checking if controller.ok has been called, the Promise returned by Promise.all of method foo was not resolved yet. Therefore you need some sort of synchronization.
One possible solution could look as follows.
it('should call controller.ok when name is set', () => {
const promises: Promise<any>[] = [];
spyOn(nameService, 'doSomething').and.callFake(n => {
const promise = Promise.resolve();
promises.push(promise);
return promise;
});
spyOn(controller, 'ok');
service.foo();
Promise.all(promises)
.then(r => expect(controller.ok).toHaveBeenCalled());
});
The same can be achieved by using fakeAsync and tick from #angular/core/testing.
it('should call controller.ok when name is set', fakeAsync(() => {
spyOn(nameService, 'doSomething').and.returnValue(Promise.resolve());
spyOn(controller, 'ok');
service.foo();
tick();
expect(controller.ok).toHaveBeenCalled();
}));

Nesting .then() functions

Is it bad practice to nest multiple then functions? It seems fairly logical to say "execute this function, and when it's done, execute this one" (and so on) but the code looks horrible.
If it helps I originally had this query in the context of firestore getting user details then getting documents
firebaseApp.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
//If error
}).then(()=>{
firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get().then((snapshot)=>{
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
})
}).then(()=>{
//Tell the user in the UI
});
});
Are there alternatives? One that springs to mind is like so
var functionOne = () =>{
console.log("I get called later");
}
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 3000);
});
promise1.then(function(value) {
functionOne();
});
But even then it seems like it could get complex after a few .then()'s
Return the Promise from the first outer .then, and then use the resolve value in a second outer .then, without any nested .thens:
firebaseApp.auth().signInWithEmailAndPassword(email, password)
.then(()=>{
return firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
})
.then((snapshot) => {
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
})
.catch((error) => {
// handle errors
});
Make sure not to catch too early - if there's an error anywhere in the chain, often you'll want to stop normal execution and go directly to the end (eg, tell the user that there was an error).
If you're worried about code readability, consider using async/await (and transpile down your production code for older browsers):
// in an async function:
try {
await firebaseApp.auth().signInWithEmailAndPassword(email, password);
const snapshot = await firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get()
snapshot.docs.forEach(doc => {
//Do stuff with data that we've just grabbed
});
//Tell the user in the UI
} catch(error) {
// handle errors
}
It depends on what you want to do: If you need access both to the result passed into then and to the result of a subsequent operation you're doing within the then at the same time, nesting is reasonable:
doSomething()
.then(result1 => {
return doSomethingElse()
.then(result2 => {
return result1 + result2;
});
})
.then(combinedResult => {
// Use `combinedResult`...
})
.catch(/*...*/);
often, though, you just need to pass a single value through the chain, by returning the promise from your subsequent operation from the then handler:
doSomething()
.then(result => {
return doSomethingElse(result);
})
.then(lastResult => {
// `lastResult` is the fulfillment value from `doSomethingElse(result)`
})
.catch(/*...*/);
Doing that resolves the promise then created to the promise returned by get() on the query. (To "resolve a promise to something" means that you've made the promise's settlement depend on the thing you've resolved it to. If you resolve it to another promise, its settlement depends on the settlement of that other promise.)
Looking at your Firebase example, I'd probably do it without nesting:
firebaseApp.auth()
.signInWithEmailAndPassword(email, password)
.then(() => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID).get())
.then((snapshot) => {
snapshot.docs.forEach(doc => {
// Do stuff with data
});
})
.then(() => {
// Tell the user in the UI
})
.catch(function(error) {
// Handle/report error, which may be from `signInWithEmailAndPassword`, your collection query, or an error raised by your code in the `then` handlers above
});
You should chain promises and, also, you can name the functions, which IMHO can improve readibility significantly. Consider something like this
const signIn = () => firebaseApp.auth().signInWithEmailAndPassword(email, password);
const onSigninError = (err) => // error handling logic here
const getCollection = () => firebaseApp.firestore().collection(collectionName).where("associatedID", "==", authID)
.get();
const processSnapshot = (snapshot) => snapshot.doc.forEach(// do stuff here
const displayMessage = () => // do stuff here
signIn()
.catch(onSigninError)
.then(getCollection)
.then(processSnapshot)
.then(displayMessage);

Categories

Resources