Unit Testing 101
Continued
Oren Rubin
Applitools
Director of R&D
Wix Web Architect
IBM Distributed Computing
Spring Diagnostics Machine Learning
Cadence Compiler Engineer
UI testing is hard.. so don't
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
Unit Isolation
Optimal
Spies
Stubs
Mocks
Unit Isolation
No app is perfect, so use
Fake ajax
Fake timers
Work asynchronously
Sinon.js
Powerful testing utility which works with ANY testing framework
Author also wrote a good book
Author Helps with Buster, an automation server
Spies
Make sure a function is called
Make sure a method is called
Make sure it was called with the appropriate params
Spy on functions
// 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);
});
Spy on functions
// 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);
});
Spy on methods
// 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);
});
Spy filters
it("spy filters", function () {
var spy = sinon.spy(a, 'bestMethodEva').withArgs(42);
a.bestMethodEva();
assert(spy.called);
});
Spy assertions
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
});
Spy assertions
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?!
});
Stubs
Test stubs are functions (spies) with pre-programmed behavior
They support the full test spy API
Stubs
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]
});
Stubs
it("force throwing an error", function () {
// Causes the stub to throw an exception (Error).
stub.throws();
stub.throws("TypeError");
stub.throws(obj);
});
Mocks
Mocks are stubs (which also means spies) with built-in expectations
Rarely used
var mock = sinon.mock(myAPI);
mock.expects("method").once().throws();
// some code
mock.verify();
Jasmine
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));
});
Jasmine
Jasmine Spies are full test doubles bundled together
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');
qUnit
load using html scaffold
Only the basic assertion
Supports fixtures
qUnit
Fixtures example
<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");
});
Fake timers.. time traveling
Mocking the JavaScript Clock
Used when the app uses setTimeout/setInterval for callbacks.
Used when the app uses setTimeout/setInterval for callbacks.
E.g., fast forward a transition.
Fake timers.. time traveling
// setup
timerCallback = jasmine.createSpy('timerCallback');
jasmine.Clock.useMock();
// test
setTimeout(function() {
timerCallback();
}, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.Clock.tick(101);
expect(timerCallback).toHaveBeenCalled();
Fake Server requests
Only Sinon has it built-in
Fake requests
Fake responses
Can go to headers level
Fake Server requests
setUp: function () {
this.xhr = sinon.useFakeXMLHttpRequest();
var requests = this.requests = [];
this.xhr.onCreate = function (xhr) {
requests.push(xhr);
};
},
tearDown: function () {
this.xhr.restore();
},
Fake Server requests
"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" }]))
}
Asynchronous
qUnit has a simple start/stop (many disadvantage)
jsTestDriver has overly smart (confusing) callback system
Jasmine has great polling (busy wait) system
Asynchronous
Jasmine
// do something
// wait for condition
// assertion
Asynchronous
Jasmine
runs( function() {
// do something
});
waitsFor(function() {
// return true when condition is set
});
runs( function() {
// assertion
});
Asynchronous
Jasmine Riddle
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);
Asynchronous
Jasmine
var ready = false;
var other;
runs( function() {
setTimeout(function() {
ready = true;
other = 5;
}, 3000);
});
waitsFor(function() {
return ready;
});
runs( function() {
assertEquals(5, otherData);
});
Asynchronous
jsTestDriver - hold your breath
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);
});
};
Tips
Put your tests next to the files
Have your own callLater (wrap setTimeout), and save the stacktrace
Show the skipped tested, so you never forget them
Don't be scared to to get familiar with the internals
Extras
Centralized Servers
Integrated tests
Centralized Servers
Take control of browsers (called slaves)
Usually a config file (rather than HTML scaffold)
E.g., jsTestDriver, buster
jsTestDriver
Integrated with IDEs (e.g., WebStorm, IntelliJ)
Remote Debugging
Coverage Tools!!
Demo!
Integration Tests
Usually start from front-end
Usually DOM based (Selenium, QTP, etc..)
Rarely image based (EggPlant, Sikuli)
Selenium
Navigates a browser automatically
Supports many languages (now even js)
ROI very low (for medium/big companies)
Selenium Hello World
// 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());
go home
Oren Rubin
shexman@gmail.com
@shexman
advanced unit testing
By shex
advanced unit testing
- 2,968