I have a leakage problem when testing my jQuery plugin. The problem occurs when I want to mock out a value or function on a literal object.
Example:
test('Overwrite some default setting', function(){
$.fn.plugin.defaults.bar = 'foo';
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
This test will fail because the first test added the 'bar' var to defaults. I fixed it with the following code but taking a copy of a copy doesn't look very elegant.
$(function(){
/*
* Trolley Button Base: Options.
*/
var defaults_copy = $.extend({}, $.fn.plugin.defaults );
var setdown = {
setup : function(){
$.fn.plugin.defaults = $.extend({}, defaults_copy);
},
teardown : function(){ }
};
module('Test leakage', setdown );
test('Overwrite some default setting', function(){
$.fn.plugin.defaults.bar = 'foo';
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
})
Also if I have a few objects in the jQuery namespace it might become a little messy if I have to take multiple copies of each object. So was wondering does anybody have a better solution to 'reset' all objects?
This is by design for QUnit. At the end of each test, you should clean up any state changes you've made. I don't know of a way to automatically do that -- you have to write the code to undo the effects of any test code you've written, like this:
test('Overwrite some default setting', function(){
// test code
$.fn.plugin.defaults.bar = 'foo';
// cleanup code
delete $.fn.plugin.defaults.bar;
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
To prevent issues with test order dependency and to isolate your unit tests fully, you will need to manually implement Test level setup and tear down functionality by creating a function for each and including it at the beginning and end of each of your test methods:
e.g.
$(document).ready(function () {
// Test Setup/TearDown
function codeUnderTestModuleTestSetup() {
//any setup needed
}
function resetDefaults() {
//code in here to reset defaults
}
function resetSomethingElse() {
//code in here to reset something else
}
function codeUnderTestModuleTestTearDown() {
resetDefaults();
resetSomethingElse();
}
//Tests
module('Your module title Test Harness');
test('FunctionUnderTest_Behaviour_ExpectedResult', 1, function () {
codeUnderTestModuleTestSetup();
//Arrange
//code removed
//Act
//Code removed
//Assert
//Code removed
codeUnderTestModuleTestTearDown();
});
}
You can manually implement module level and test run level setup and teardown functions too, if necessary.
Related
When it comes to spying on jQuery functions (e.g. bind, click, etc) it is easy:
spyOn($.fn, "bind");
The problem is when you want to spy on $('...') and return defined array of elements.
Things tried after reading other related answers on SO:
spyOn($.fn, "init").andReturn(elements); // works, but breaks stuff that uses jQuery selectors in afterEach(), etc
spyOn($.fn, "merge").andReturn(elements); // merge function doesn't seem to exist in jQuery 1.9.1
spyOn($.fn, "val").andReturn(elements); // function never gets called
So how do I do this? Or if the only way is to spy on init function how do I "remove" spy from function when I'm done so afterEach() routing doesn't break.
jQuery version is 1.9.1.
WORKAROUND:
The only way I could make it work so far (ugly):
realDollar = $;
try {
$ = jasmine.createSpy("dollar").andReturn(elements);
// test code and asserts go here
} finally {
$ = realDollar;
}
Normally, a spy exists for the lifetime of the spec. However, there's nothing special about destroying a spy. You just restore the original function reference and that's that.
Here's a handy little helper function (with a test case) that will clean up your workaround and make it more usable. Call the unspy method in your afterEach to restore the original reference.
function spyOn(obj, methodName) {
var original = obj[methodName];
var spy = jasmine.getEnv().spyOn(obj, methodName);
spy.unspy = function () {
if (original) {
obj[methodName] = original;
original = null;
}
};
return spy;
}
describe("unspy", function () {
it("removes the spy", function () {
var mockDiv = document.createElement("div");
var mockResult = $(mockDiv);
spyOn(window, "$").and.returnValue(mockResult);
expect($(document.body).get(0)).toBe(mockDiv);
$.unspy();
expect(jasmine.isSpy($)).toEqual(false);
expect($(document.body).get(0)).toBe(document.body);
});
});
As an alternative to the above (and for anyone else reading this), you could change the way you're approaching the problem. Instead of spying on the $ function, try extracting the original call to $ to its own method and spying on that instead.
// Original
myObj.doStuff = function () {
$("#someElement").css("color", "red");
};
// Becomes...
myObj.doStuff = function () {
this.getElements().css("color", "red");
};
myObj.getElements = function () {
return $("#someElement");
};
// Test case
it("does stuff", function () {
spyOn(myObj, "getElements").and.returnValue($(/* mock elements */));
// ...
});
By spying on the window itself you have access to any window properties.
As Jquery is one of these you can easily mock it as below and return the value you require.
spyOn(window, '$').and.returnValue(mockElement);
Or add a callFake with the input if it needs to be dynamic.
how to write unit test for variables in a angular js file.
fooFactory.spec.js
..
describe('test fooFactory', function(){
it('test if statement', function(){
expect(?).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});
..
fooFactory.js
(function () {
angular.module('MyApp').factory('fooFactory', fooFactory);
function fooFactory(someOtherFile){
var testVar = someOtherFile.someOtherfunc;
if(testVar ){
// want to test this code. has 10 line of code
}
...
function foo(){
//does something and I can test this
}
...
return {
foo:foo
}
}
})();
how do i assign values to testVar before the if statement runs
if(testVar ){
// how do I test this code?
}
Should I encapsulate the entire if in a function and pass it through the return.
bar();
function bar(data){
if(data){
testVar = data;
}
if(testVar ){
// how do I test this code?
}
}
return {
foo: foo,
bar: bar
}
Is there a better way to do this.
Or should the js file have setters and getters in the first place. Thanks
you need to inject someOtherFile (which is, if I understand correctly a Service too) into fooFactory when creating it.
So have something like this in your test if you want to completly mock someOtherFile
describe('test fooFactory', function(){
var fooFactory;
beforeEach(function(){
fooFactory = new FooFactory(
{ someOtherfunc: function() { return true; } }
);
stateChangeCallback = $rootScope.$on.calls.first().args[1];
});
it('test if statement', function(){
expect(fooFactory).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});
However, if you need someOtherFile and you don't want to mock all its responses, what you can do is use angular dependancy injection to inject this service and then only mock someOtherfunc on it. That will give something like this:
describe('test fooFactory', function(){
var fooFactory;
var someOtherFile;
beforeEach(inject(function (
_someOtherFile_
) {
someOtherFile = _someOtherFile_;
fooFactory = new FooFactory(
someOtherFile
);
}));
it('test if statement', function(){
spyOn(someOtherFile, 'someOtherfunc').and.returnValue(true);
expect(?).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});
You cannot test functions/variable that are not accessible outside your factory.
The proper way of doing it would be to expose it. But be aware that you should not be exposing everything just to make it testable. You should really consider if adding a test for that function/variable will actually add value to your application.
I have some tightly coupled legacy code that I want to cover with tests. Sometimes it's important to ensure that one mocked out method is called before another. A simplified example:
function PageManager(page) {
this.page = page;
}
PageManager.prototype.openSettings = function(){
this.page.open();
this.page.setTitle("Settings");
};
In the test I can check that both open() and setTitle() are called:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page", function() {
expect(this.page.open).toHaveBeenCalledWith();
});
it("sets page title to 'Settings'", function() {
expect(this.page.setTitle).toHaveBeenCalledWith("Settings");
});
});
But setTitle() will only work after first calling open(). I'd like to check that first page.open() is called, followed by setTitle(). I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
I can hack up something like this:
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
// track the order of methods called
this.calls = [];
this.page.open.and.callFake(function() {
this.calls.push("open");
}.bind(this));
this.page.setTitle.and.callFake(function() {
this.calls.push("setTitle");
}.bind(this));
this.manager.openSettings();
});
it("opens page before setting title", function() {
expect(this.calls).toEqual(["open", "setTitle"]);
});
This works, but I'm wondering whether there is some simpler way to achieve this. Or some nice way to generalize this so I wouldn't need to duplicate this code in other tests.
PS. Of course the right way is to refactor the code to eliminate this kind of temporal coupling. It might not always be possible though, e.g. when interfacing with third party libraries. Anyway... I'd like to first cover the existing code with tests, modifying it as little as possible, before delving into further refactorings.
I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
Looks like the Jasmine folks saw this post, because this functionality exists. I'm not sure how long it's been around -- all of their API docs back to 2.6 mention it, though none of their archived older style docs mention it.
toHaveBeenCalledBefore(expected)
expect the actual value (a Spy) to have been called before another Spy.
Parameters:
Name Type Description
expected Spy Spy that should have been called after the actual Spy.
A failure for your example looks like Expected spy open to have been called before spy setTitle.
Try this:
it("setTitle is invoked after open", function() {
var orderCop = jasmine.createSpy('orderCop');
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.calls.count()).toBe(2);
expect(orderCop.calls.first().args[0]).toBe('firstInvoke');
expect(orderCop.calls.mostRecent().args[0]).toBe('secondInvoke');
}
EDIT: I just realized my original answer is effectively the same as the hack you mentioned in the question but with more overhead in setting up a spy. It's probably simpler doing it with your "hack" way:
it("setTitle is invoked after open", function() {
var orderCop = []
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop.push('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop.push('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.length).toBe(2);
expect(orderCop[0]).toBe('firstInvoke');
expect(orderCop[1]).toBe('secondInvoke');
}
Create a fake function for the second call that expects the first call to have been made
it("opens page before setting title", function() {
// When page.setTitle is called, ensure that page.open has already been called
this.page.setTitle.and.callFake(function() {
expect(this.page.open).toHaveBeenCalled();
})
this.manager.openSettings();
});
Inspect the specific calls by using the .calls.first() and .calls.mostRecent() methods on the spy.
Basically did the same thing. I felt confident doing this because I mocked out the function behaviors with fully synchronous implementations.
it 'should invoke an options pre-mixing hook before a mixin pre-mixing hook', ->
call_sequence = []
mix_opts = {premixing_hook: -> call_sequence.push 1}
#mixin.premixing_hook = -> call_sequence.push 2
spyOn(mix_opts, 'premixing_hook').and.callThrough()
spyOn(#mixin, 'premixing_hook').and.callThrough()
class Example
Example.mixinto_proto #mixin, mix_opts, ['arg1', 'arg2']
expect(mix_opts.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(#mixin.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(call_sequence).toEqual [1, 2]
Lately I've developed a replacement for Jasmine spies, called strict-spies, which solves this problem among many others:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.spies = new StrictSpies();
this.page = this.spies.createObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page and sets title to 'Settings'", function() {
expect(this.spies).toHaveCalls([
["open"],
["setTitle", "Settings"],
]);
});
});
I am a front end developer trying to learn Test driven development. I've built a simple js calculator using jQuery/jasmine.
From what I learned I started writing my test cases first (in jasmine).
describe("calculator", function() {
it("add correctly", function() {
expect(add(2,3)).toEqual(5);
});
it("subtract correctly", function() {
expect(sub(2,3)).toEqual(-1);
});
describe("divide", function(){
it("divided correctly", function(){
expect (divide(2,3)).toEqual(0.6666666666666666);
});
it("divided by 0 gives infite", function(){
expect (divide(2,0)).toEqual(Infinity);
});
});
describe("toggle sign", function(){
it("toggle to - sign", function() {
expect (toggleSign(2)).toEqual(-2);
});
it("toggle to + sign", function() {
expect (toggleSign(-2)).toEqual(2);
});
});
});
then pass them with minimal code
(function(window, document, undefined){ "use strict";
window.add = function(a,b){ return a+b; };
window.sub = function(a,b){ return a-b; };
window.divide =function(a,b){ return (a/b); };
window.toggleSign = function(a){ return -a; };
}(window, document));
I was all happy and content until I actually started building the app
Here is what it looks like
http://niteshsharma.com/jscalc/
The most sensible way I could come up with, to write a calculator, is to create a simple string of the complete operation and eval it on execution
window.press = function(a){
$("#display").html(function(i, oldHtml){
return oldHtml+a;
});
};
window.execute= function(){
try{
$("#display").html( new Function( "return " + $("#display").html())() );
}
catch(err){
alert("error");
}
};
How could I write a test case for such code?
If some one could explain to me the correct process of doing TDD (with my example of the calculator) I would appreciate it a lot.
Here's your answer. Via jQuery, you can dynamically add your "display" element to the page, and then execute your press and execute functions and do assertions based on the contents of the display element. Here's some tests.
describe("press", function(){
it("add-remove display element", function(){
// Dynamically add a span element with id="display"
$('body').append($('<span id="display">').text('0'));
expect($('#display').length).toEqual(1);
// Clean up after yourself here - tests should be atomic
$('#display').remove();
expect($('#display').length).toEqual(0);
});
it("add-remove display element", function(){
$('body').append($('<span id="display">').text('0'));
// With the display element present, run the press function
press('2');
expect($('#display').html()).toEqual('02');
$('#display').remove();
});
});
describe("execute", function(){
it("execute a couple presses and run a calculation", function(){
$('body').append($('<span id="display">').text('0'));
// With the display element present, run the press function
press('2');
press('+');
press('3');
execute();
expect($('#display').html()).toEqual('5');
$('#display').remove();
});
});
If I may suggest as well, it's not a good idea to add your calculator functions to the window object. You could do something like this perhaps (untested stub code):
function Calculator(){
// Private members
var firstNumber = 0;
var secondNumber = 0;
function toggleSign(){}
// Public members
return {
press: function(){},
execute: function(){}
};
}
// To use it, instatiate a new calculator and call its public methods
var calc = new Calculator();
calc.press('2');
calc.press('+');
calc.press('3');
calc.execute();
Also, you should avoid executing strings like you're doing in your execute method... Inside your Calculator class, you should have private variables to store the first number and second number, and then just do math on them without having to execute strings.
Hope this helps.
Andy
I've written basic jQuery plugins before, but I'm struggling to get my head around something more complex. I'm looking to emulate the API of jQuery UI, which works like this:
$('#mydiv').sortable({name: 'value'}); // constructor, options
$('#mydiv').sortable("serialize"); // call a method, with existing options
$('#mydiv').sortable('option', 'axis', 'x'); // get an existing option
I've tried the following:
(function($){
$.fn.myPlugin = function(cmd){
var config = {
default: 'defaultVal'
};
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
What I would like to see here is 'newval' being logged, but I'm seeing 'defaultVal' instead; the plugin is being called and started from scratch every time I call .myPlugin() on the element.
I've also tried using _foo.call(this) and some other variants. No joy.
In a way, I understand why this is happening, but I know that it must be possible to do it the same way as jQuery UI. I just can't see how!
(I appreciate that jQuery UI uses the widget factory to handle all of this, but I don't want to make that a requirement for the plugin.)
Perhaps what you want is this...
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
Move the config variable outside the myPlugin function. This change will cause config to be initialized only once: when your plugin function is created.
You're declaring config during the function call rather than as a closure used by it. Try this:
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
In addition, you could look into the jQuery data API for caching data, especially if you aren't going to have just one instance per page.