I've written a lot of asynchronous unit tests lately, using a combination of Angular's fakeAsync, returning Promises from async test body functions, the Jasmine done callback, etc. Generally I've been able to make everything work in a totally deterministic way.
A few parts of my code interact in deeply-tangled ways with 3rd party libraries that are very complex and difficult to mock out. I can't figure out a way to hook an event or generate a Promise that's guaranteed to resolve after this library is finished doing background work, so at the moment my test is stuck using setTimeout:
class MyService {
public async init() {
// Assume library interaction is a lot more complicated to replace with a mock than this would be
this.libraryObject.onError.addEventListener(err => {
this.bannerService.open("Load failed!" + err);
});
// Makes some network calls, etc, that I have no control over
this.libraryObject.loadData();
}
}
it("shows a banner on network error", async done => {
setupLibraryForFailure();
await instance.init();
setTimeout(() => {
expect(banner.open).toHaveBeenCalled();
done();
}, 500); // 500ms is generally enough... on my machine, probably
});
This makes me nervous, especially the magic number in the setTimeout. It also scales poorly, as I'm sure 500ms is far longer than any of my other tests take to complete.
What I think I'd like to do, is be able to tell Jasmine to poll the banner.open spy until it's called, or until the test timeout elapses and the test fails. Then, the test should notice as soon as the error handler is triggered and complete. Is there a better approach, or is this a good idea? Is it a built-in pattern somewhere that I'm not seeing?
I think you can take advantage of callFake, basically calling another function once this function is called.
Something like this:
it("shows a banner on network error", async done => {
setupLibraryForFailure();
// maybe you have already spied on banner open so you have to assign the previous
// spy to a variable and use that variable for the callFake
spyOn(banner, 'open').and.callFake((arg: string) => {
expect(banner.open).toHaveBeenCalled(); // maybe not needed because we are already doing callFake
done(); // call done to let Jasmine know you're done
});
await instance.init();
});
We are setting a spy on banner.open and when it is called, it will call the callback function using the callFake and we call done inside of this callback letting Jasmine know we are done with our assertions.
Related
I am reading some tutorials on promise tests in mocha. There is a piece of codes:
before(function(done) {
return Promise.resolve(save(article)).then(function() {
done();
});
});
Why done() called in the then() in the before()? What is the difference between the above codes and the following codes:
before(function(done) {
return Promise.resolve(save(article));
});
Thanks
UPDATE
My question is to compare with the following codes:
before(function() {
return Promise.resolve(save(article));
});
Sorry for the typo.
The first code snippet with the before hook returns a promise and calls done. In Mocha 3.x and over, it will result in this error:
Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
It used to be that it did not particularly matter if you used done and returned a promise, but eventually the Mocha devs figured that specifying both done and returning a promise just meant the test designer made a mistake and it was better to have Mocha pitch a fit rather than silently allow it.
In your 2nd snippet, you have the done argument and return a promise but Mocha will still wait for done to be called and will timeout. (It really should detect the argument and raise an error like in the 1st case, but it doesn't...)
Generally, if you are testing an asynchronous operation that produces a promise, it is simpler to return the promise than use done. Here's an example illustrating the problem:
const assert = require("assert");
// This will result in a test timeout rather than give a nice error
// message.
it("something not tested properly", (done) => {
Promise.resolve(1).then((x) => {
assert.equal(x, 2);
done();
});
});
// Same test as before, but fixed to give you a good error message
// about expecting a value of 2. But look at the code you have to
// write to get Mocha to give you a nice error message.
it("something tested properly", (done) => {
Promise.resolve(1).then((x) => {
assert.equal(x, 2);
done();
}).catch(done);
});
// If you just return the promise, you can avoid having to pepper your
// code with catch closes and calls to done.
it("something tested properly but much simpler", () => {
return Promise.resolve(1).then((x) => {
assert.equal(x, 2);
});
});
With regards to the completion of asynchronous operations, it works the same whether you are using it, before, beforeEach, after or afterEach so even though the example I gave is with it, the same applies to all the hooks.
I am not sure if I understood 100% the question, but the tests will not start until done is called.
beforeEach(function(done) {
setTimeout(function() {
value = 0;
done();
}, 1);
});
This test will not start until the done function is called in the call to beforeEach above. And this spec will not complete until its done is called.
it("should support async execution of test preparation and expectations", function(done) {
value++;
expect(value).toBeGreaterThan(0);
done();
});
You don't have to pass done in your example, just:
before(function() {
return Promise.resolve(save(article));
});
If you do pass done the test runner will expect to be called before continue, otherwise it will probably throw a timeout error.
In this particular case there is no functional difference. The callback, often called done, was introduced to handle asynchronous tests when using callbacks. Returning a Promise is sufficient, but note that you cannot define the done callback, because the test suite will wait until it's called. Use done when you can't easily return a Promise. In your case the second test will be infinite, because you define done, which you never actually call.
I have a very basic Jasmine test using Q, and it doesn't seem to be working. I'm simply using the Jasmine mock clock and trying to use Q.delay, which I think uses setTimeout under the hood.
I had some more complex tests that involved calling setTimeout() from a Q promise's then handler, and that didn't seem to work either, but I thought this would make a simpler test case to post on Stack Overflow.
Here's my very simple test case:
it('clock test', function() {
jasmine.clock().install();
var foo = null;
Q.delay('hi', 10000).then(function(arg) {
console.log('foo');
foo = arg;
});
jasmine.clock().tick(10010);
expect(foo).toEqual('hi');
jasmine.clock().uninstall();
});
(This test was based on the test case found in a similar SO question: Jasmine clock tick & Firefox: failing to trigger a Q.delay method)
When I run the test, it fails saying Expected null to equal 'hi'. The console.log never even executes.
To see if the problem was with Q or something else, I tried adding a simple setTimeout call inside the spec:
setTimeout(function() {
console.log("bar");
}, 10000);
This worked - bar was printed to the console after the call to jasmine.clock().tick.
After the Jasmine clock is uninstalled, the normal clock kicks in, and after waiting 10 seconds, then the foo gets printed out.
Anyone have any idea what's going on?
You are asking it to do async in a sync way. Don't. Try using Jasmine's async instead and drop using the clock.
it('clock test', function(done) {
var foo = null;
Q.delay('hi', 10000).then(function(arg) {
console.log('foo');
foo = arg;
expect(foo).toEqual('hi');
done();
});
}, 15000);
Anyone have any idea what's going on?
Probably Q is serious about the asynchrony guarantee given by in the Promises/A+ spec. So even when setTimeout executes earlier than expected, that is no reason to suddenly call then callbacks synchronously. It still will need to wait a tick after the promise is fulfilled.
An alternative explanation would be that Q took its private copy of setTimout during its module initialising, to prevent exactly such messing around with builtins. Instead of calling the global function, it would use its internal reference to the old function, and not be affected by jasmine.clock() at all.
You can spyOn Q.delay and avoid having your test wait for the given delay:
describe('Q.delay', function() {
it('should call Q.delay', function(done) {
spyOn(Q.makePromise.prototype, 'delay').and.returnValue(function() {
return this;
});
Q('A resolve value').delay(3000).then(done);
expect(Q.makePromise.prototype.delay).toHaveBeenCalledWith(3000);
});
});
Would I need to use a setTimeout? (And it works when I do). The problem I'm having is that the FeedObject does an asynchronous call, so when a new instance of it is created, it takes sometime before it can inject itself to the DOM.
describe('Default Case', function () {
before(function () {
$divHolder = $('#divHolder');
$divHolder.html('');
var myComponent = new FeedObject({
div: $divHolder[0],
prop: {
id: 'myFeed',
},
client: {}
});
myComponent.createDOMElements();
});
it('should exist', function () {
console.log($divHolder.find('.feed-item')); // This does not exist unless I use a timeout
});
EDIT: Just to clarify, the new FeedObject does an AJAX call.
What you need to determine when new FeedObject is done. Depending on what FeedObject provides, it could mean:
Providing a callback that it called when new FeedObject is done. Call done in the callback.
Listening to an event (most likely a custom jQuery event). Call done in the event handler.
Make the before hook return a promise that exists on the object returned by new FeedObject. For instance, the ready field could contain this promise, and you'd do return myComponent.ready. Mocha will wait for the promise to be resolved. (Remove the done argument from your before hook.)
I strongly suggest that you add one of the facilities above rather than use setTimeout. The problem with setTimeout is that it is always suboptimal: either you wait longer than you need for the DOM to be ready, or you set a timeout which is super close to the real time it take to be ready but in some cases the timeout will be too short (e.g. if your machine happens to have a spike of activity that does not leave enough CPU for the browser running the test code) and the test will fail for bogus reasons.
Well setTimeOut will work (until the server response takes longer than the time you specified and then it won’t work) but it’s definitely not the best or fastest way to do it. When you’re using an async call, you need to define a callback method that runs after the code is finished. The idea of the code should be something like this:
describe('Default Case', function () {
// Your code goes here
$.ajax = function(ajaxOpts)
{
var doneCallback = ajaxOpts.done;
doneCallback(simulatedAjaxResponse);
};
function fetchCallback(user)
{
expect(user.fullName).to.equal("Tomas Jakobsen");
done();
};
fetchCurrentUser(fetchCallback);
});
If you need even more details you could check this very helpful link. Hope that helps!
I would use this async chai plugin. If you are using a promise api (probably the most straight-forward way to handle async testing), this makes testing async ops very simple. You just have to remember to return any invocation that is async (or returns a promise) so chai knows to 'wait' before continuing.
describe(() => {
let sut
beforeEach(() => {
sut = new FeedObject()
return sut.createDomElements() // <- a promise
})
it('should exist', () => {
$('#target').find('.feed-item').should.exist
})
})
Also consider the goals of this testing: why are you doing it? I find that a lot of DOM insert/remove/exists testing is wasted effort. This is particularly true if you are testing a 3rd party library/framework as part of your application code. The burden of proof is on the library/framework to prove it is correct (and most well-written libraries already have a testing suite), not your app. If you are concerned about testing 'did I correctly invoke this 3rd party code' there are better ways to do that without touching the DOM.
I am trying to test the postMessage API as there is a slight delay before message are receive i can not run expectation right after sending a message.
In jasmine 1.3 i used to wait() a few milliseconds before running expectation and that worked fine. However with jasmine 2.0 wait() is deprecated and it now seems that everything inside a setTimeout do not get run unless done() is called, witch in my case doesn't cut it as i actually want to wait real time before running my expectation..
Not sure if that all make sense, if it does I'd love some pointers on how I could go about this.
Thanks!
This works for me:
beforeAll(function (done) {
setTimeout(done, 5000);
});
The beforeAll function occurred first, but it will end when you invoke the done callback function. So if you use setTimeout function with 5000, it will wait 5000 milliseconds before continuing.
Rather than waiting some number of milliseconds, jasmine has hooks to wait until a function returns. This page has some good example, and I've copied one here to show a specific way of testing ajax callbacks. Just add a spy as a callback to your function and wait for that callback to be executed.
it("should make a real AJAX request", function () {
var callback = jasmine.createSpy();
makeAjaxCall(callback);
waitsFor(function() {
return callback.callCount > 0;
}, "The Ajax call timed out.", 5000);
runs(function() {
expect(callback).toHaveBeenCalled();
});
});
EDIT:
Since you're testing that your application makes a specific callback, you can just replace that callback with a spy instead of creating a new one like I did.
Jasmine 2.0 added a "done" style callback, so you should be able to do something like: (I haven't tested the syntax of this but hopefully a good start)
it("should make an ajax callback with jasmine 2.0", function(done)) {
// this is the object you are testing - assume it has the ajax method you want to call and the method that gets called when the ajax method is finished
var myObject
spyOn(myObject, "callback").andCallFake(function() {
done();
});
myObject.makeAjaxCall();
}
I am fairly new to javascript and am trying to use jasmine to unit test some error-handling code.
In particular, I'm trying to write some tests that verify that our custom code (called windowHandleError) that replaces window.onerror() gets called, and is doing what we want it to.
I've tried something along the lines of:
it("testing window.onerror", function() {
spyOn(globalerror, 'windowHandleError');
globalerror.install();
var someFunction = function() {
undefinedFunction();
};
expect(function() {someFunction();}).toThrow();
expect(globalerror.windowHandleError).toHaveBeenCalled();
});
But it doesn't trigger the onerror. There are some related questions I've looked at, but they seem to ask about specific browsers, or how/where to use onerror instead of how to test it.
window.onerror not firing in Firefox
Capturing JavaScript error in Selenium
window.onerror does not work
How to trigger script.onerror in Internet Explorer?
Based on what some of those said, I thought running the spec tests in a debugger would force the onerror to trigger, but no dice. Anyone know a better approach to this?
I recently developed small JavaScript error handler with unit tests based on Buster.JS which is similar to Jasmine.
The test that exercises the window.onerror looks like this:
"error within the system": function (done) {
setTimeout(function() {
// throw some real-life exception
not_defined.not_defined();
}, 10);
setTimeout(function() {
assert.isTrue($.post.called);
done();
}, 100);
}
It throws a real-life error within a setTimeout callback which will not stop the test execution and will check that a spy was called after 100ms in another setTimeout and then call done() which is how you test async functionality with Buster.JS. The same approach is available with Jasmine by using done() in async tests.
Without knowledge of Jasmine.
All unit tests run inside a try/catch block so that if one test dies, the next test can run (True for QUnit at least). And since window.onerror doesn't catch exceptions that is already caught inside a try/catch, it will not run when testing this in a unittest.
Try calling the onerror function manually based on the exception.
try {
//Code that should fail here.
someUndefinedFunction();
} catch (e) {
window.onerror.call(window, e.toString(), document.location.toString(), 2);
}
expect(globalerror.windowHandleError).toHaveBeenCalled();
This is far from perfect as document.location is not the same as the url argument, and you need to manually set line number. A better way would be to parse e.stack for the correct file and line number.
When calling the function like this inside a unit test, it might be a better idea to simply test that your function is set and that it functions properly when called with all faked arguments.