sinon: when stubs restored? - javascript

I am using sinon 1.17.6. Below is my codes:
it('should', sinon.test(function(/*done*/) {
const stubtoBeStubbedFunction = this.stub(this.myObj, 'toBeStubbedFunction');
const instance = {
id: 'instanceId',
tCount: 3,
};
console.log('0 toBeStubbedFunction', this.myObj.toBeStubbedFunction);
return this.myObj.toBeTestedFunction(instance)
.then(() => {
console.log('3 toBeStubbedFunction', this.myObj.toBeStubbedFunction);
expect(stubtoBeStubbedFunction.calledOnce).to.be.true();
// done();
});
}));
MyClass.prototype.toBeTestedFunction = function toBeTestedFunction(input) {
const metaData = {};
this.log.debug('toBeTestedFunction');
if (input.tCount === 0) {
console.log('1', this.toBeStubbedFunction);
return bluebird.resolve((this.toBeStubbedFunction(metaData)));
}
return this.myClient.getData(input.id)
.bind(this)
.then(function _on(res) {
if (res) {
console.log('2', this.toBeStubbedFunction);
this.toBeStubbedFunction(metaData);
}
})
.catch(function _onError(err) {
throw new VError(err, 'toBeTestedFunctionError');
});
};
console.log output:
0 toBeStubbedFunction toBeStubbedFunction
2 function toBeStubbedFunction(meta) {
// real implementation
}
3 toBeStubbedFunction function toBeStubbedFunction(meta) {
// real implementation
}
It seems that during test running, the stubbed function is restored. I thought sinon.test() should restore stubs after returned promise resolved or caught (stubs should be restored after console.log('2', this.toBeStubbedFunction); run). Why? I used done to solve my issue. But are there better solutions? I may use mocha and sinon the wrong way.
Any comments welcomed. Thanks

The sinon.test method provided by Sinon 1.x completely ignores promises returned by tests, which means it does not wait for the promise to resolve before resetting the sandbox it creates.
Sinon 2.x removed sinon.test from Sinon and spun it as its own separate sinon-test package. It initially had the same problem as the sinon.test method provided by Sinon 1.x but the problem was reported and resolved.
The peer dependencies of sinon-test are currently setting a minimum version for Sinon of 2.x so at a minimum you'd have to upgrade Sinon and add sinon-test to your test suite to have support for promises and use the test wrapper provided sinon-test.
Or you could abandon the wrapper and write your own before/beforeEach and after/afterEach hooks that would take care of creating and resetting a sandbox for you. I've used Sinon for years (certainly from the 1.x series onwards, possibly even earlier) and that's always what I've done. I did not even know about sinon.test until I saw the other question you asked about it a few days ago.

Related

Jest "Async callback was not invoked within the 5000 ms timeout" with monkey-patched `test` and useFakeTimers

This setup is extremely specific but I couldn't find any similar resources online so I'm posting here in case it helps anyone.
There are many questions about Jest and Async callback was not invoked, but I haven't found any questions whose root issue revolves around the use of jest.useFakeTimers(). My function should take no time to execute when using fake timers, but for some reason Jest is hanging.
I'm using Jest 26 so I'm manually specifying to use modern timers.
This is a complete code snippet that demonstrates the issue.
jest.useFakeTimers('modern')
let setTimeoutSpy = jest.spyOn(global, 'setTimeout')
async function retryThrowable(
fn,
maxRetries = 5,
currentAttempt = 0
) {
try {
return await fn()
} catch (e) {
if (currentAttempt < maxRetries) {
setTimeout(
() => retryThrowable(fn, maxRetries, currentAttempt + 1),
1 * Math.pow(1, currentAttempt)
)
}
throw e
}
}
describe('retryThrowable', () => {
const fnErr = jest.fn(async () => { throw new Error('err') })
it('retries `maxRetries` times if result is Err', async () => {
jest.clearAllMocks()
const maxRetries = 5
await expect(retryThrowable(() => fnErr(), maxRetries)).rejects.toThrow('err')
for (let _ in Array(maxRetries).fill(0)) {
jest.runAllTimers()
await Promise.resolve() // https://stackoverflow.com/a/52196951/3991555
}
expect(setTimeoutSpy).toHaveBeenCalledTimes(maxRetries)
})
})
The full error message is
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
at mapper (../../node_modules/jest-jasmine2/build/queueRunner.js:27:45)
Any ideas would be very appreciated
edit 1: I have tried --detectOpenHandles but no new information is provided
edit 2: I just tried my above code snippet in a fresh project and realized that it passes just fine. So the issue must somewhere else in my Jest config. I'll answer my own question when I determine the root cause
My issue ended up being in my jest configuration.
We execute tests directly against an in-memory DB, and to keep our tests clean we wrap each test in a DB transaction. Jest doesn't provide a native aroundEach hook like many other test runners, so we achieved this by monkey-patching the global test and it functions so we could execute the test callback inside a transaction. Not sure if it matters but to be explicit we are using Sequelize as our ORM and for transactions.
The test I was executing (as seen above) recursively called setTimeout with a function which threw an error / rejected a Promise. Sequelize transactions apparently do not appreciate unhandled rejections, and it was causing the test to hang. I never was able to get to the root of why the test was hanging; the transaction successfully rolled back and all test expectations were run, but for some reason the test never exited.
Solution #1 (not great)
My first solution is not pretty but it is pragmatic. I simply extended the Jest test function with a variant which does not use the monkey-patched test.
// jest.setup.ts
declare namespace jest {
interface It {
noDb: (name: string, fn?: ProvidesCallback, timeout?: number) => void
}
}
it.noDb = it
// jest.config.js
module.exports = {
// ...
setupFilesAfterEnv: [
'./jest.setup.ts', // <-- inject `it.noDb` method
'./jest.mokey-patch.ts', // <-- monkey-patching
],
}
Then, I modified the test from the OP to call this new function
it.noDb('retries `maxRetries` times if result is Err', ...
More details on how and why this extension works can be found in this blog post.
Solution #2 (better)
After messing with this more, I realized that the root issue was that there were unhandled promise rejections happening in the main thread. I'm not sure why this conflicted with Sequelize Transactions but suffice to say it's bad practice anyway.
I was able to avoid the issue entirely, as well as any bizarre Jest extensions, by simply fixing the method to only throw on the first call. This way, we can handle errors when we call retryThrowable but do not throw errors on subsequent calls.
// ...
try {
return await fn()
} catch (e) {
if (currentAttempt < maxRetries) {
setTimeout(
() => retryThrowable(fn, maxRetries, currentAttempt + 1),
1 * Math.pow(1, currentAttempt)
)
}
// 💡 this is the new part
if (currentAttempt === 0) {
throw e
}
}
// ...

Unit test handler that contains promise

I have TDD knowledge, and I've been trying to start a project in javascript applying the same principles.
I am building an API that, once hit, fires a request to an external service in order to gather some data, and once retrieved, parses it and returns the response.
So far, I've been unlucky with my saga, I've searched a lot and the most similar issue on SO I've found is this one. But I've had no success in order to apply the same solution.
My implementation on the implementation side is as follows:
//... other handlers
weather(request, response) {
//... some setup and other calls
this.externalService.get(externalURL)
.then(serviceResponse => {
this.externalResponseParser.parse(serviceResponse)
});
//... some more code
}
And on the test side:
let requester;
let mockParser;
let handler;
let promiseResolve;
let promiseReject;
beforeEach(function () {
requester = new externalRequesterService();
mockParser = sinon.createStubInstance(...);
handler = new someHandler({
externalService: requester,
externalResponseParser: mockParser
});
});
it('returns data', function () {
sinon.stub(requester, 'get').callsFake((url) => {
return new Promise((resolve, reject) => {
// so I can be able to handle when the promise gets resolved
promiseResolve = resolve;
promiseReject = reject;
});
});
handler.weather(request, response);
// assertions of what happens before externalService.get gets called - all green
promiseResolve(expectedServiceResponse);
assert.ok(mockExternalResponseParser.parse.calledOnce, 'Expected externalResponseParser.parse to have been called once');
});
In the last line of the test, it fails, even though I am calling what I am supposed to.
At some point I've added some logging, and I was able to see that the code of the then block, seems to get executed after the assertion in the test, which might be source of the problem.
I've tried to find out if there is some sort of eventually that could be used, so my assertion after resolving the promise would be something like:
assert.eventually(mockExternalResponseParser.parse.calledOnce, 'Expected externalResponseParser.parse to eventually be called once');
but no luck.
Does anyone have some clear explanation of what is missing? Many thanks in advance
P.S.- As per request, please find a stripped down version of my code here. Just run npm install, followed by npm test in order to get the same output.
Thank you all for the time spent.
I ended up finding this very good article on Medium which allowed me to solve my issue. It has a very nice explanation moving from callbacks to promises, which was exactly the scenario I had in hands.
I have updated the github repository I created on how to fix it.
If you want to TL;DR here goes just the highlight of the changes. Implementation side:
async weather(request, response) {
//...
let serviceResponse = await this.requesterService.get(weatherURL);
//...
};
And on the test side:
it('returns weather on success', async function () {
sinon.stub(requester, 'get').callsFake((_) => {
return new Promise((resolve, _) => {
resolve(expectedServiceResponse);
});
});
await handler.weather(request, response);
//...
assert.ok(mockWeatherParser.parseWeather.calledOnce, 'Expected WeatherParser.parseWeather to have been called once'); // no longer fails here
//...
});
Now keep in mind in this example, it is still synchronous. However, I evolved my API already step by step, and after migrating to this synchronous version using Promises, it was way more easier to migrate to an async version. Both in terms of testing and implementation.
If you have the same issue and need help or have questions, let me know. I'll be happy to help.

Jest testing with Node - Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout

I'm starting to test my code with Jest, and I can't make a seemingly simple test to pass. I am simply trying to check if what I receive from a Maogoose database request is an object.
The function fetchPosts() is working because I hooked it up with a React frontend and it is displaying the data correctly.
This is my function fetchPosts():
module.exports = {
fetchPosts() {
return new Promise((resolve, reject) => {
Posts.find({}).then(posts => {
if (posts) {
resolve(posts)
} else {
reject()
}
})
})
}
}
And my test:
it('should get a list of posts', function() {
return posts.fetchPosts().then(result => {
expect(typeof result).toBe('object')
})
})
This makes the test fail, and Jest says
'Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.'
QUESTION: How can I make this test pass?
You can expect asynchronous results using resolves, as shown in the Jest documentation.
In your case:
it('should get a list of posts', function() {
const result = posts.fetchPosts();
expect(result).resolves.toEqual(expect.any(Object));
})
…although I have a suspicion your list of posts is actually an array, so you probably want this:
it('should get a list of posts', function() {
const result = posts.fetchPosts();
expect(result).resolves.toEqual(expect.any(Array));
})
Another tip: You don't need to wrap the body of your fetchPost in an additional promise, you can simply return the promise you get from Posts.find and add a then to it, like this:
module.exports = {
fetchPosts() {
return Posts.find({}).then(posts => {
if (posts) {
return posts;
}
throw new Error('no posts'); // this will cause a promise rejection
})
}
}
It's also highly possible that you're not getting a response back from the DB at all from your test suite. Test suite's can call different environmental variables / configs that lead to different calls. This error can also be seen if no response is returned, as in - if someone blocks your IP from connecting, on and on.
Also if you are simply looking to increase the timeout, then you can do that by setting
jest.setTimeout(10000);
You can use this statement in beforeEach if you want to change the timeout for all your tests in that describe block or in the test/it/spec block if you want it for a single test.
For me none of the above worked so I tried older version of jest and it worked
npm i -D jest#25.2.7.
if you are using it with typescript make sure to degrade ts-jest as well
npm i -D jest#25.2.7 ts-jest#25.3.1

Unit testing async functions in Ember controllers

I've hit a very weird problem: I'm trying to make unit tests to achieve 100% testing coverage on my application.
And of course I wrote some tests for my controllers but it seems like there is no way to test anything async in Ember (2.4.0) using ember-cli.
I have a function in controller that does this:
readObject() {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
}.bind(this));
}
I'm writing a test that should cover this function.
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject();
assert.equal(cont.get('property1'), 'someValue);
});
Obivously, this assert wouldn't work because readObject() is async call but this isn't the root of the problem. The problem is that then a callback in this.store.findRecord is being executed - my controller is already destroyed! So I get "calling set on destroyed object" error there.
In other words - even if I wrap my function in a promise and reformat both functions like this:
readObject() {
return new Promise(function(resolve) {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
resolve();
}.bind(this));
}.bind(this));
}
and
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject().then(function() {
assert.equal(cont.get('property1'), 'someValue);
});
});
It wouldn't work, because after executing readObject() my controllers become immediately destroyed, not waiting for any callbacks.
So, it could be any async call instead of store.findRecord - it could be Ember.run.later, for example.
Does anybody had the same issue? I've read a lot of articles I can't believe that Ember with such a big community doesn't provide a way to make async unit tests.
If anyone has any clues - please give me a hint, cause I'm kinda lost here. At the moment I have two thoughts:
I'm making controllers wrong, Ember doesn't suppose any async operations inside of it. But even if I move async calls to services - I hit the same problem with writing unit tests for them.
I have to decompose my functions to
readObject() {
this.store.findRecord('myModel',1).then(this.actualReadObject.bind(this));
}
actualReadObject(obj) {
this.set('property1',obj.get('property2');
}
to have at least callbacks body covered with tests, but this means I never get 100% testing coverage in my app.
Thank you in advance for any clues. :)
I had a similar problem and looking at the QUnit API - async I solved it. Try out following:
// ... in your controller ...
readObject() {
return this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1', obj.get('property2');
}.bind(this));
}
// ... in your tests, either with assert.async: ...
const done = assert.async(); // asynchronous test due to promises usage
Ember.run(function(){
subject.readObject().then(function(){
// once you reach this point you are sure your promise has been resolved
done();
});
});
// or without assert.async
let promise;
Ember.run(function(){
promise = subject.readObject();
});
return promise;
In a case of unit tests I do also mock other dependencies, for example: store.
this.subject({
property1: null,
store: {
findRecord(modelName, id){
assert.equal(modelName, "myModel1");
assert.equal(id, 1);
return new Ember.RSVP.Promise(function(resolve){
resolve(Ember.Object.create({ property2: "a simple or complex mock" }));
})
}
}
});
I am not sure about the second case (the one without assert.async). I think it would work too, because the test suite returns a promise. This gets recorgnized by QUnit that waits for the promise.
I copy my own solution here, cause code formatting in comments isn't too good.
test('async', function (assert) {
const cont = this.subject();
const myModel = cont.get('store').createRecord('myModel'); //
// Make store.findRecord sync
cont.set('store',{
findRecord(){
return { then : function(callback) { callback(myModel); } }
}
});
// Sync tests
assert.equal(cont.get('property2'), undefined);
cont.readObject(); // This line calls store.findRecord underneath
assert.equal(cont.get('property2'), true);
});
So, I just turned store.findRecord into a sync function and it runs perfect. And again - many thanks to Pavol for a clue. :)

Angular.js promise not resolving when unit testing service with karma

I am trying to unit test an Angular.js service, and need to set an expect on a promise returned from a Mock service (using Jasmine). I am using the karma unit testing framework. The relevant code snippet is below:
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(false).toBeTruthy(); // If this test passes, there is something going wrong!
expect(token).not.toBeNull(); // The token should be ValidToken
expect(token).toBe('ValidToken');
});
The complete unit test code can be seen here.
The problem is the promise.then statement never fires when karma is executing. Hence, none of my expect statements are executed.
In my controller tests, I use $scope.$digest() to resolve the promises, but I am not clear on how to do this in a service test. As I thought there was no notion of 'scope' in a service test.
Do I have the wrong end of the stick here? Do I need to injecct $rootScope into my service test and then use $digest? Or, is there another way?
I had this problem and resolved it by simply putting a
$rootScope.$apply() at the end of my test
Your FacebookService might be the issue, as suggested by #mpm. Are you sure it doesn't have any http calls happening inside of that Facebook dependency which wouldn't be occurring during unit testing? Are you certain that resolve has been called on the deferred yet?
Assuming that you are using ngFacebook/ngModule a quick note before the solution/ideas is that this project does not have unit tests ! Are you sure you want to use this project ?
I did a quick scan of your Unit Tests on Github and found following missing:-
1) Module initialization.
ngFacebook needs that or you need to initialize your module that does the same thing.
beforeEach(module('ngFacebook'));
OR
beforeEach(module('yieldtome'));
2) Seriously consider mocking ngFacebook module
At unit level tests you are testing your code within a mocked bubble where outside interfaces are stubbed out.
Otherwise) Try adding calling the API as below:-
$rootScope.$apply(function() {
this.FacebookService.getFacebookToken().then(function(){
//your expect code here
});
});
$httpBackend.flush();//mock any anticipated outgoing requests as per [$httpBackend][2]
beforeEach(function(){
var self=this;
inject(function($rootScope,Facebook){
self.$rootScope=$rootScope;
self.Facebook=Facebook;
});
})
it('resolves unless sourcecode broken',function(done){
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = this.FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(token).toBe('ValidToken');
done();
});
$rootscope.$apply();
});
https://docs.angularjs.org/api/ng/service/$q
I agree with the above answers that a service should have nothing to do with $rootScope.
In my case had a $q promise, that used a second service internally resolving to a promise as well. No way to resolve the external one, unless I added $rootScope.$digest() into my service code (not the test)...
I ended-up writing this quick shim for $q to use in my tests, but be careful, as it's just an example and not a complete $q implementation.
beforeEach(module(function($provide) {
$provide.value('$q', {
defer: function() {
var _resolve, _reject;
return {
promise: {
then: function (resolve, reject) {
_resolve = resolve;
_reject = reject;
}
},
resolve: function (data) {
window.setTimeout(_resolve, 0, data);
},
reject: function (data) {
window.setTimeout(_reject, 0, data);
}
};
}
});
}));
Hope it will be useful to someone, or if you have any feedback.
Thanks.

Categories