In my app that uses backbone I have the following function:
handleModelSaved: function () {
var redirect_location = 'redirect_path';
App.Messenger.success('Report was saved successfully!');
setTimeout(function () {
window.location = redirect_location;
}, 2000);
}
For now I have the following test:
describe('handleModelSaved', function () {
beforeEach(function () {
view = initV3View();
spyOn(App.Messenger, 'success');
view.handleModelSaved();
});
it("sends a success message", function () {
expect(App.Messenger.success).toHaveBeenCalledWith('Report was saved successfully!');
});
});
And my question is, how can I test with Jasmine this code:
setTimeout(function () {
window.location = redirect_location;
}, 2000);
You can just check the location property on the window object to have a specific value. Of course, you can only do this after the timeout has exprired. What you probably want to do is to override the setTimeout function with your own implementation that calls the specified callback function immediately. With that you can also check whether or not the setTimeout was called with the expected timeout. You can do this yourself or use Jasmine's Clock functions to mock the native timeout functions: http://jasmine.github.io/2.0/introduction.html
Another option is to use the done function that is passed as an argument to your it() function callback. Which makes the test asynchronous.
Related
I have the following functions:
function getPersonData(id) {
retrieveData(
id,
function(person) {
if(person.name) {
displayPerson(person);
}
}
}
function retrieveData(id, successCallBack) {
executeRequest(id, {
success: successCallBack
});
}
getPersonData retrieves a person's information based on the id. It in turn calls retrieveData by passing in the id and a successCallBack function.
retrieveData takes the id and successCallBack and calls another function, executeRequest, which gets the data and passes back a person object.
I am trying to test getPersonData and have the following spec set up
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
spyOn(projA.data, "retrieveData").and.returnValue(
{
'name': 'john'
}
);
spyOn(projA.data, "displayPerson");
projA.data.getPersonData("123");
});
it("displays the person's details", function() {
expect(projA.data.displayPerson).toHaveBeenCalled();
);
});
But when the spec is executed the displayPerson method isn't called. This is because the person data being passed back from the success callBack function(person) isn't being passed in even though I have mocked retrieveData to return a result.
My question is:
Is this the right way to test callBack functions? Either way what am I doing wrong?
Ok, so jasmine is tricky in a lot of subtle ways and I think there's two main issues with your code
You have way too many asynchronous calls wrapped in each other. Which is by itself not a problem, but it makes testing in JASMINE hell of a lot harder. For example, what is the point of having a retrieveData function which just calls executeRequest function with the exact same parameters but in a slightly different way.
I rewrote your getPersonData to be like this
function getPersonData(id) {
// this is needed to properly spy in Jasmine
var self = this;
//replaced retrieveData with just execute request
// self is required to properly spy in Jasmine
self.executeRequest(id, {
success: function(person) {
if (person.name) {
self.displayPerson(person);
}
}
})
}
//I don't know what exactly executeRequest does
//but I took the liberty to just make it up for this example
function executeRequest(id, callback) {
callback.success({
name: id
});
}
//I also assumed that projA would look something like this
var projA = {
data: {
getPersonData: getPersonData,
retrieveData: retrieveData,
displayPerson: displayPerson,
executeRequest: executeRequest
}
};
2. In order to test asynchronous code in Jasmine, you need to include a done callback with the test. Also, if you expect a callback function to fire automatically, you need to set it up within a setTimeout function, otherwise it will never fire. Here's an adjusted example:
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
//you should not spyOn retriveData or executeCode because it will override the function you wrote and will never call displayPerson
// you should only have spies on the methods you are testing otherwise they will override other methods
spyOn(projA.data, "displayPerson");
});
it("displays the person's details", function(done) {
// it's better to call the function within specific test blocks so you have control over the test
projA.data.getPersonData("123");
// at this point, it will just have called the getPersonData but will not call executeRequest
setTimeout(function() {
//this block will just call executeRequest
setTimeout(function() {
//this block will finally call displayPerson
expect(projA.data.displayPerson).toHaveBeenCalled();
//tell jasmine the test is done after this
done();
})
})
});
})
I have a javascript below, which appends a DIV on page load and hides it after 3 sec.
var testObj = {
initialize: function() {
var that = this;
$(window).on("load", function() {
(function ($) { //Append Div
$('body').append("<div>TEST</div>");
})(jQuery);
that.hideAppendedDiv();
});
},
hideAppendedDiv: function() { //Hide appended Div after 3s
setTimeout(function(){
$("div").hide();
}, 3000);
}
};
//call Initialize method
testObj.initialize();
How to write Jasmine test cases for the methods in the code.
I'm guessing that you don't really want to test a Javascript function such as $(window).on('load')... , but that your own function hideAppendedDiv() get's called from $(window).on('load'). Furthermore, you want to make sure that the function hideAppendedDiv() works as well.
IMO, you need two expects.
Somewhere in your setup beforeEach function:
beforeEach(function () {
spyOn(testObj , 'hideAppendedDiv').and.callThrough();
});
Expectations
it('expects hideAppendedDiv() to have been called', function () {
// make the call to the initialize function
testObj.initialize ();
// Check internal function
expect(testObj.hideAppendedDiv).toHaveBeenCalled();
});
it('expects hideAppendedDiv() to hide div', function () {
// make the call to the hideAppendedDiv function
testObj.hideAppendedDiv();
// Check behavior
expect(... check the div ...)
});
Edit
Just to be clear, Jasmine executes all the expects in order. Now, if you have two functions fn_1(), and fn_2() and you want to test that they were called in order you can setup yet another spi function that returns a specific value, or a sequential and incremental set of values every time it is called.
beforeEach(function () {
spyOn(testObj , 'fn_1').and.returnValues(1, 2, 3);
spyOn(testObj , 'fn_2').and.returnValues(4, 5, 6);
});
The first time fn_1 is called it will return 1, respectively fn_2 will return 4.
That is just one of the ways, but you have to get creative when testing.
Now if you want to test that a function was called after x amount of time here is a post that already explains it.
You don't need to test the window load event, if you move the append code out of the anonymous function call and pass it into the event handler instead you can test the functionality in exactly the same way you would anything else and your code will be better structured.
I'm trying to write a plugin for Jasmine that allows you to return a promise from a spec and will pass or fail that spec depending on whether or not the promise is fulfilled or rejected.
Of course, I want to write tests to make sure that my plugin works correctly, and to be thorough, I need to make sure that tests fail when the promise is rejected... so how do I make a test pass when I need to make sure that a test "successfully fails"?
After a conversation with the developers who work on Jasmine, we've come up with this:
var FAILED = 'failed'
var PASSED = 'passed'
describe('My Test Suite', function () {
var env
beforeEach(function () {
// Create a secondary Jasmine environment to run your sub-specs in
env = new jasmine.Env()
})
it('should work synchronously', function () {
var spec
// use the methods on `env` rather than the global ones for sub-specs
// (describe, it, expect, beforeEach, etc)
env.describe('faux suite', function () {
spec = env.it('faux test', function (done) {
env.expect(true).toBe(true)
})
})
// this will fire off the specs in the secondary environment
env.execute()
// put your expectations here if the sub-spec is synchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
})
// don't forget the `done` argument for asynchronous specs
it('should work asynchronously', function (done) {
var spec
// use the methods on `env` rather than the global ones.
env.describe('faux suite', function () {
// `it` returns a spec object that we can use later
spec = env.it('faux test', function (done) {
Promise.reject("FAIL").then(done)
})
})
// this allows us to run code after we know the spec has finished
env.addReporter({jasmineDone: function() {
// put your expectations in here if the sub-spec is asynchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
// this is how Jasmine knows you've completed something asynchronous
// you need to add it as an argument to the main `it` call above
done()
}})
// this will fire off the specs in the secondary environment
env.execute()
})
})
Going off Joe's answer, I moved the fake test context into a single function. Since the code under test is making use of jasmine expectations, I load the inner Env into jasmine.currentEnv_ and call it explicitly with jasmine.currentEnv_.expect(). Note that currentEnv_ is an internal variable set by jasmine itself, so I can't guarantee that this won't be broken in a future jasmine version.
function internalTest(testFunc) {
var outerEnvironment = jasmine.currentEnv_;
var env = new jasmine.Env();
jasmine.currentEnv_ = env;
var spec;
env.describe("fake suite", function () {
spec = env.it("fake test", function () {
func();
});
});
env.execute();
jasmine.currentEnv_ = outerEnvironment;
return spec.result;
}
Then each test looks like
it("does something", function () {
//Arrange
//Act
var result = internalTest(function () {
//Perform action
});
//Assert
expect(result.status).toBe("failed"); //Or "success"
expect(result.failedExpectations.length).toBe(1);
expect(result.failedExpectations[0].message).toBe("My expected error message");
});
I was trying to put together a small example to show co-workers but can't figure out what's wrong with this test that I've put in a gist.
Essentially I want to test a function that does something async, but use Sinon's spy() functionality to assure it completes:
function asyncHello(name, delay, cb) {
setTimeout(function() {
console.log("running after ", delay);
cb("hello " + name);
}, delay);
}
suite('Mega Suite', function(){
suite("testing async hello", function() {
test('should call the callback', function(done) {
var cb = sinon.spy();
asyncHello("foo", cb);
cb.should.have.been.called();
done();
});
});
});
Thought using Mocha and done() to resolve a test that depends on an async function (setTimeout, in this case) would work, but maybe someone can point out where I'm wrong. Thanks!
You don't need Sinon for this:
function asyncHello(name, delay, cb) {
setTimeout(function() {
console.log("running after ", delay);
cb("hello " + name);
}, delay);
}
suite('Mega Suite', function(){
suite("testing async hello", function() {
test('should call the callback', function(done) {
asyncHello("foo", 1000, function () {
done();
});
});
});
});
There were two problems in this code:
You called asyncHello("foo", cb); which made it so that your delay argument inside the function was set to cb and the cb argument inside the function was undefined.
Even after fixing the 1st item cb.should.have.been.called(); was called before the function passed to setTimeout could execute.
You basically don't need to use Sinon because if you just set a callback to call done() then you know the test is successful. If there's a problem anywhere done() won't get called and the test will fail.
Given the following code snippet, how would you create a Jasmine spyOn test to confirm that doSomething gets called when you run MyFunction?
function MyFunction() {
var foo = new MyCoolObject();
foo.doSomething();
};
Here's what my test looks like. Unfortunately, I get an error when the spyOn call is evaluated:
describe("MyFunction", function () {
it("calls doSomething", function () {
spyOn(MyCoolObject, "doSomething");
MyFunction();
expect(MyCoolObject.doSomething).toHaveBeenCalled();
});
});
Jasmine doesn't appear to recognize the doSomething method at that point. Any suggestions?
Alternatively, as Gregg hinted, we could work with 'prototype'. That is, instead of spying on MyCoolObject directly, we can spy on MyCoolObject.prototype.
describe("MyFunction", function () {
it("calls doSomething", function () {
spyOn(MyCoolObject.prototype, "doSomething");
MyFunction();
expect(MyCoolObject.prototype.doSomething).toHaveBeenCalled();
});
});
When you call new MyCoolObject() you invoke the MyCoolObject function and get a new object with the related prototype. This means that when you spyOn(MyCoolObject, "doSomething") you're not setting up a spy on the object returned by the new call, but on a possible doSomething function on the MyCoolObject function itself.
You should be able to do something like:
it("calls doSomething", function() {
var originalConstructor = MyCoolObject,
spiedObj;
spyOn(window, 'MyCoolObject').and.callFake(function() {
spiedObj = new originalConstructor();
spyOn(spiedObj, 'doSomething');
return spiedObj;
});
MyFunction();
expect(spiedObj.doSomething).toHaveBeenCalled();
});