What is the state-of-the-art approach to testing jQuery code? - javascript

I'm new to JavaScript. Imagine, I have following piece of code:
$(document).ready(function() {
$('#nights').on('keyup', function() {
var nights = +$(this).val();
var dailyPrice = +$(this).closest(".tour").data("daily-price");
$('#total').text(nights * dailyPrice);
$('#nights-count').text($(this).val());
});
});
How can I unit-test that the anonymous function
retrieves the value of the current element,
retrieves the data from +$(this).closest(".tour").data("daily-price") and
then calls text(...) on $('#total') and $('#nights-count')
?
Note that I'm interested in unit tests (therefore creating a full-fledged a Selenium test suite, which types in something and then checks the value of the elements isn't suitable for me), which don't require me to add a new abstraction layer.
By abstraction layer I mean this: I could create class JQueryAbstraction and then 2 sub-classes - one, which calls real jQuery methods and another, which just counts the number of calls.

You can try introducing the popular Unit Testing lightweight frameworks like Jasmine, Mocha, QUnit
All these framework can co-exist with AngularJS, JQuery....etc
From your example, which you are trying with an anonymous function, you can convert that function to a non-anonymous function and pass it to the testing tool
to make sure the last line is reached I am introducing a simple global variable and assigning value to it, say done = true or done = false
Example:
var done = false;
var onReady = function() {
$('#nights').on('keyup', function() {
var nights = +$(this).val();
var dailyPrice = +$(this).closest(".tour").data("daily-price");
$('#total').text(nights * dailyPrice);
$('#nights-count').text($(this).val());
});
done = true;
}
$(document).ready(onReady);
So now you can test your onReady code with unit testing as in Jasmine, you can comment the actual document ready
//$(document).ready(onReady);
And call the function via unit testing
describe("Document Ready Unit Testing", function() {
it("call the function ", function() {
done = false;
onReady() ;
expect(done).toEqual(true);//this will print the unit testing passed or failed
});
});
But: If you are not interested in declaring the anonymous function separately then you can loop through the document object and get the ready event function.

Related

Angular Unit Test, SpyOn stand-alone function

I am using Jasmine to test an Angular app and would like to test that the getItem() function within my controller is called when the ready() function of the controller is called.
--- Controller ---
var vm = this;
vm.items = [];
$ionicPlatform.ready(ready);
function ready() {
vm.items.push(getItem());
function getItem(){
var item = //do stuff to get item;
console.log('getItem called');
return item;
}
}
--- Spec ---
describe('Controller', function(){
//--- Load app with dependencies and module to test code omitted.
beforeEach(function(){
//How do I spy on getItem() to test that it was called?
//I've tried getItem = jasmine.createSpy()
//I've tried spyOn(window, 'getItem')
}
//--- Initialize the controller and a mock scope code omitted.
beforeEach(function(done){
$ionicPlatform.ready(function(){
done();
});
});
it('getItem function should get called', function(){
expect(getItem).toHaveBeenCalled();
//--- Note, getItem does not get called according to the expect statement,
//--- but does output 'getItem called' to the terminal when running the test.
});
});
Unfortunately, you've come upon a fundamental limit of Javascript unit testing with Jasmine-- you can only spyOn methods that are exposed on some object. If there is a function that is internal to another function, and not exposed in anyway, you cannot test it directly.
However, you do have two options available to you:
Expose the function in a way that it can be spied on (generally as a method of whatever Angular component you are testing).
Test it indirectly.
The first is probably relatively self-evident, but the latter may be a little confusing. Basically, you can't test directly if the getItems function is called, but the function may have downstream methods it calls or values it changes you can test. For instance, you can test that vm.items.push is larger after ready is called, or you can spyOn(console.log) and expect(console.log).toHaveBeenCalledWith('getItem called').
You can find arguments for both approaches on the internet-- I tend to prefer approach two because I don't like doing refactors solely for the purpose of testability, but many will argue that refactoring for testability generally yields better code. That choice is yours to make. Hope this helps!

Jasmine Spies.and.stub method

I've been reading through the Jasmine documentation and I've been struggling to understand what the Spies .and.stub method actually does. English is not my native language, so I don't even know what the word "stub" actually means, and there is no translation for it in my language.
In the documentation it says:
When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub.
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, 'setBar').and.callThrough();
});
it("can call through and then stub in the same spec", function() {
foo.setBar(123);
expect(bar).toEqual(123);
foo.setBar.and.stub();
bar = null;
foo.setBar(123);
expect(bar).toBe(null);
});
});
What does and.stub actually do and how is it useful?
For the term, you can look at wikipedia : http://en.wikipedia.org/wiki/Test_stub
In a nutshell it's a "fake" object that you can control that replaces a "real" object in your code.
For the function, what I understand is that and.stub() removes the effect of and.callThrough() on a spy.
When you call and.callThrough, the spy acts as a proxy, calling the real function, but passing through a spy object allowing you to add tests like expectation.
When you call and.stub, or if you never call and.callThrough, the spy won't call the real function. It's really usefull when you don't want to test an object's behavior, but be sure that it was called. Helping you to keep your test truly unitary.
To complete the previous answer:
Indeed, it's not clear from the doc, but it's very clear in the source code:
https://github.com/jasmine/jasmine/blob/4be20794827a63ca5295848c58ffc478624ee4dc/src/core/SpyStrategy.js
plan = function() {};
-> the called function is empty
this.callThrough = function() {
plan = originalFn;
-> the called function is the original function
this.stub = function(fn) {
plan = function() {};
-> the called function is empty (again)

Is it possible to mock the window.location object for a qUnit test?

Let's say I have a utility function that, for simplicity's sake (the real thing is complicated and irrelevant), returns the current window's querystring.
var someUtilityFunction = () {
return window.location.search.substring(1);
};
Now I want to unit test this function in qUnit (not sure if the testing harness is relevant or not):
test('#1 someUtilityFunction works', function () {
// setup
var oldQS = window.location.search;
window.location.search = '?key1=value1&key2=value2&key3=value3';
var expectedOutput = 'key1=value1&key2=value2&key3=value3';
// test
equals(someUtilityFunction(),
expectedOutput,
'someUtilityFunction works as expected.');
// teardown
window.location.search = oldQS;
});
The problem here is that setting the window.location.search to a different querystring is causing the page to reload, essentially entering an infinite request loop. Is there any way to mock out the window.location object without making any changes to the someUtilityFunction function?
We run into the same problem a few days ago. Mainly there are 2 approaches:
Rewrite your code
This might not be the best (if any) solution, but consider passing the window object to your function to make mocking easier. Even better, use a closure and encapsulate your code. This has a few more advantages:
You can shadow global vars
You can use private local vars
You can avoid naming collisions
The shadowing makes mocking really easy, just pass in something else
Wrap your code
You can wrap all your code inside a function which mocks the window object into a local variable. You have basically two possibilities there as well:
Suppose this is the mock:
var customWindow = {
location: {
search: "",
hash: ""
}
};
Use a closure
var someUtilityFunction;
(function(window) {
// window is now shadowed by your local variable
someUtilityFunction = () {
return window.location.search.substring(1);
};
})(customWindow);
This shadows the global window with a local window.
Use the with statement
Although I am usually strongly against with, it could really solve a lot of problems here. Since it basically remaps your scope, you can very easily mock your environment.
// first some more preparation for our mock
customWindow.window = customWindow;
with(customWindow) {
// this still creates the var in the global scope
var someUtilityFunction = () {
// window is a property of customWindow
return window.location.search.substring(1);
};
// since customWindow is our scope now
// this will work also
someUtilityFunction = () {
// location is a property of customWindow too
return location.search.substring(1);
};
}
By the way: I don't know if the search property suffers from the same symptoms as the hash property - namely sometimes including the question mark and sometimes not. But you might want to consider using
window.location.search.replace(/^\?/, "");
instead of
window.location.substr(1);
I've had some success using window.history.pushState. See this StackOverflow answer. For each unit test, I call a function setQueryString('var=something') which I then implement like this:
function setQueryString(queryString) {
window.history.pushState({}, '', '?' + queryString);
}
You'll need to clear the query string with the afterEach method of QUnit.module, otherwise you're query string will be set to the value of the final test, and you'll get weird results.

How to verify a certain javascript function has been called during unit testing

I'm using JsTestDriver and a bit of Jack (only when needed). Does anyone know how to verify that a javascript function has been called during unit testing?
E.g.
function MainFunction()
{
var someElement = ''; // or = some other type
anotherFunction(someElement);
}
And in the test code:
Test.prototype.test_mainFunction()
{
MainFunction();
// TODO how to verify anotherFunction(someElement) (and its logic) has been called?
}
Thanks.
JavaScript is very powerful language in terms that you can change the behaviour at runtime.
You can replace anotherFunction with your own during test and verify it has been called:
Test.prototype.test_mainFunction()
{
// Arrange
var hasBeenCalled = false;
var old = anotherFunction;
anotherFunction = function() {
old();
hasBeenCalled = true;
};
// Act
MainFunction();
// Assert (with JsUnit)
assertEquals("Should be called", true, hasBeenCalled);
// TearDown
anotherFunction = old;
}
The note: You should be aware that this test modifies the global function and if it will fail it may not always restore it.
You'd probably better pick JsMock for that.
But in order to use it you need to separate the functions and put them into objects, so you would not have any global data at all.

Unit testing jQuery document.ready function [duplicate]

This question already has answers here:
How to invoke $(document).ready(function() {}) in unit testing
(5 answers)
Calling $(document).ready(function() {...}); from another file
(1 answer)
Closed 5 months ago.
I have a question in regards to unit testing jQuery's document.ready function().
Currently I have 2 scenarios in my code:
function myFunction()
{
$(document).ready(function() { ... });
}
And:
$(document).ready(function()
{
// some really long setup code here
});
I tried to write a unit test for the first scenario, but I just couldn't get it to run into the document.ready function. As for the second scenario, I haven't come up with a way to test it yet (I'm having trouble coming up with both a way to test it and the syntax).
So assuming I cannot change the source code, are there any ways to test those functions? (assuming it is a good idea to test them)
Thanks.
You do not need to test $(document).ready as it is part of the framework and is already unit tested. When writing unit tests you need to test two things:
Your interaction with the framework. This includes things like making sure that you call the right functions with the right parameters.
Your own code - that your code does the right thing.
So what you really need to do is to make sure that whatever code that gets called from $(document).ready is correct.
function myInit(){
//...
}
function myFunction()
{
$(document).ready(myInit);
}
All you need to do now is to unit test myInit function.
What you can also do is mock out $.ready function to make sure that you are calling it:
var readyCalled = false;
$.ready = function(func){
readyCalled = (myInit == func);
}
//Your code containing `myInit` will get executed somewhere here
//....
//Then test:
test("Should have called ready", function() {
ok(readyCalled, "ready should have been called with myInit as a parameter.")
});
The function that registers the on ready handler should register another function, not an anonymous codeblock. Then you can test the code that calls $.ready() separate from the code that runs on ready. So you have:
One test to verify the right function is set as the the ready handler
Another test to verify the ready handler does the right stuff
To test scenario 1, you'll need to inject a test double for jQuery. This is difficult as if you redefine $ or jQuery, odds are you'll screw up other code that relies on it for other processing (like the test runner). At the same time your code may still want to call jQuery directly when its using utility methods like array concatenation. Any inversion-of-control pattern should address this though (http://martinfowler.com/articles/injection.html).
Anyhow, here's some code using constructor injection (using JSMock for the mocking library, and QUnit (of jQuery) for the test runner):
// the code
var createComponent = function(_$) {
var that = {};
that.OnStart = function() {
_$.ready(this.OnReady);
};
that.OnReady = function() {
};
return that;
};
// the test
test("OnStart associates the ready handler", function() {
var sut;
var mock$ = mc.createMock($);
mock$.expects().ready(isA.TypeOf(Function)).andStub(function(callback) {
equals(callback, sut.OnReady);
});
sut = createComponent(mock$);
sut.OnStart();
mc.verify();
});
test("OnReady does the right stuff", function() {
//etc
});
I use this general pattern for all event handlers in JS... You might prefer to use prototype type classes. When you pass functions as parameters to jQuery, you need to be aware that the "this" value will not be set by jQuery when those callbacks are called. In the test, this breaks because equals(callback, sut.OnReady) no longer passes. To address this, you need to make the event handlers direct members of each instance. You can imagine when there are a number of then its nice to have a util that takes a list of them, but this demonstrates making 'OnReady' a member who does not rely on 'this'.
var Component = function(_$) {
this._$ = _$;
// repeat for each event handler thats tested
this.OnReady = function() {
Component.prototype.OnReady.apply(this);
}
}
Component.prototype.Start = function() {
this._$.ready(this.OnReady);
}
Component.prototype.OnReady = function() {
}

Categories

Resources