JavaScript is mostly used for UI/UX
Testing the appearance is hard (bad ROI)
Don't check styles
Avoid checking for classes
Separate business logic and UI (follow MVC pattern)
Even separate the view to template and logic
Don't be scared of refactoring code
Unit Testing is on case of "reuse" of your code
Spies
Stubs
Mocks
Fake ajax
Fake timers
Work asynchronously
Make sure a function is called
Make sure a method is called
Make sure it was called with the appropriate params
// Function under test
function doSomething() {
console.log('I am doing something');
}
it("calls the original function", function () {
var spy = sinon.spy(doSomething);
spy(); // log : 'I am doing something'
assert(spy.called);
});
// Function under test
function callSomething(callback) {
callback();
}
function doSomething() {
console.log('I am doing something');
}
it("spy on ", function () {
var spy = sinon.spy(doSomething);
callSomething(doSomething); // log : 'I am doing something'
assert(spy.called);
});
// Object under test
var a = {
bestMethodEva: function() {
console.log('I am doing something');
}
};
it("spy on method", function () {
var spy = sinon.spy(a, 'bestMethodEva');
a.bestMethodEva();
assert(spy.called);
});
it("spy filters", function () {
var spy = sinon.spy(a, 'bestMethodEva').withArgs(42);
a.bestMethodEva();
assert(spy.called);
});
it("spy assertions", function () {
var spy = sinon.spy(a, 'bestMethodEva');
a.bestMethodEva();
assert(spy.called);
assert(spy.calledOnce);
assertEqual(1, spy.callCount)
assert(spy.calledBefore(sinon.spy()));
assertFalse(spy.threw());
assertFalse(spy.returned(undefined));
// calledWithExactly does deep compare
assert(spy.calledWithExactly(otherObj));
// warning! read the API, checks if ONE invocation is similar
});
it("spy riddle", function () {
// load our component
domElement.addEvent('click', a.bestMethodEva);
// spy
var spy = sinon.spy(a, 'bestMethodEva');
// stimulate
domElement.click();
// assert
assert(spy.called); // fails!! why?!
});
it("force a different value", function () {
// Creates an anonymous stub function.
var stub = sinon.stub();
// Replace method
var stub = sinon.stub(object, "method");
// return specific value by:
var stub = sinon.stub(object, "method", func);
// or
stub.returns(obj);
// or
stub.returnsArg(index); // return argument[index]
});
it("force throwing an error", function () {
// Causes the stub to throw an exception (Error).
stub.throws();
stub.throws("TypeError");
stub.throws(obj);
});
var mock = sinon.mock(myAPI);
mock.expects("method").once().throws();
// some code
mock.verify();
Very simple
Great docs: http://pivotal.github.com/jasmine/
Load is by an HTML scaffold
Completely standalone
Custom Matchers: expect('moshe').toBeMoshe();
Easy to filter tests
Nested describe blocks (each has beforeEach)
it("spy on method", function () {
var spy = sinon.spy(a, 'bestMethodEva');
a.bestMethodEva();
assert(spy.called);
assert(spy.calledOnce);
assertEqual(1, spy.callCount)
assert(spy.calledBefore(sinon.spy()));
assertFalse(spy.threw());
assertFalse(spy.returned(undefined));
});
var yoSpy = spyOn(obj, "f");
var yoSpy = spyOn(obj, "f").andReturn(745)
var yoSpy = spyOn(obj, "f").andCallThrough();
var yoSpy = spyOn(obj, "f").andCallFake(function(value) {
return value;
});
obj.yo();
expect(yoSpy).toHaveBeenCalled();
expect(yoSpy).toHaveBeenCalledWith('moshe');
<div id="qunit-fixtures">
<ul/>
</div>
test('test add item', function() {
var list = $('ul');
list.append("<li>moshe</li>")
equal(1, list.children().length, "1 child");
});
test('test reset', function() {
var list = $('ul');
equal(0, list.children().length, "no children");
});
// setup
timerCallback = jasmine.createSpy('timerCallback');
jasmine.Clock.useMock();
// test
setTimeout(function() {
timerCallback();
}, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.Clock.tick(101);
expect(timerCallback).toHaveBeenCalled();
setUp: function () {
this.xhr = sinon.useFakeXMLHttpRequest();
var requests = this.requests = [];
this.xhr.onCreate = function (xhr) {
requests.push(xhr);
};
},
tearDown: function () {
this.xhr.restore();
},
"test should fetch comments from server" : function () {
var callback = sinon.spy();
myLib.getCommentsFor("/some/article", callback);
assertEquals(1, this.requests.length);
this.requests[0].respond(200, {
"Content-Type": "application/json" },
'[{ "id": 12, "comment": "Hey there" }]');
assert(callback.calledWith([{ id: 12, comment: "Hey there" }]))
}
// do something
// wait for condition
// assertion
runs( function() {
// do something
});
waitsFor(function() {
// return true when condition is set
});
runs( function() {
// assertion
});
console.log(1);
runs( function() {
console.log(2);
// do something
});
console.log(3);
waitsFor(function() {
console.log(4);
// return true when condition is set
});
console.log(5);
runs( function() {
console.log(6);
// assertion
});
console.log(7);
var ready = false;
var other;
runs( function() {
setTimeout(function() {
ready = true;
other = 5;
}, 3000);
});
waitsFor(function() {
return ready;
});
runs( function() {
assertEquals(5, otherData);
});
AsyncTest.testSomethingComplicated = function(queue) {
var state = 0;
queue.call('increment in 5 seconds', function(callbacks) {
var myCallback = callbacks.add(function() {
++state;
});
window.setTimeout(myCallback, 5000);
});
queue.call('check other stuff', function() {
assertEquals(1, state);
});
};
// also supports ChromeDriver, FirefoxDriver, and IEDriver
WebDriver driver = new HtmlUnitDriver();
// And now use this to visit Google
driver.get("http://www.google.com");
// Find the text input element by its name
WebElement element = driver.findElement(By.name("q"));
// Enter something to search for
element.sendKeys("Cheese!");
// Now submit. WebDriver will find the form for us from the element
element.submit();
// Check the title of the page
System.out.println("Page title is: " + driver.getTitle());