1
JASMINEWhy JavaScript tests don’t smell fishy?
WHAT I WILL TALK ABOUT:
2
• What is Unit Testing
• Solution: Jasmine - whole syntax with examples
• WebStorm goodies
• Benefits of Units Tests
• Why people don’t test JavaScript?
• Test Driven Development, Behavior Driven Development
WHY PEOPLE DON’T TEST JAVASCRIPT ?
• project not big enough
• project too complex
• asynchronous XHR requests
• DOM manipulation
• too many browsers, platforms, devices
3
UNIT TESTING is a program (test case or test specification) that
isolates and tests small and specific functional unit of code.
Test one small functionality per suite. Not too many things at one time.
Remember! It impossible to write test for every case - try to cover every
reasonable case, remember about corner cases
4
GOOD PRACTICES
BENEFITS OF USING JAVASCRIPT TESTS
5
• QA phase is cheaper - you will uncover bugs earlier
• Creates great documentation
• As a developer you will write better code
• Shows that JS should work as was designed
• Quick and easy to run - try to do it with manual testing
• Runs the same every time
TDD & BDD
Behavior Driven Development: agile software development technique
testing from business value perspective, why some code is necessary
and what its goal is
6
Test Driven Development: write tests against specification, watch your
test fail, write some code, test, refactor, test-fix-implement
TDD vs BDD
Example: 10 sorting methods
TDD:
one test per one method - focused on „how” each method works
BDD:
one test per all methods - focused on the goal
give an array, sort, result
7
JASMINE MATCHERS - EQUALITY
expect(true).toEqual(true);
expect({}).toEqual({});
8
toEqual
JASMINE MATCHERS - IDENTITY
var spot = { species: "Border Collie" };
var cosmo = { species: "Border Collie" };
// success; equivalent
expect(spot).toEqual(cosmo);
// failure; not the same object
expect(spot).toBe(cosmo);
// success; the same value
expect(2).toBe(2);
toBe
checks if two things are the same
value and type, using ===
Primitive Types vs Reference Types
primitive will be give you true, reference will give you false
9
JASMINE MATCHERS - BOOLEAN
expect(true).toBeTruthy();
expect(12).toBeTruthy();
expect({}).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeFalsy();
expect("").toBeFalsy();
//false, 0, “”, undefinded, null, NaN
toBeTruthy toBeFalsy10
JASMINE MATCHERS - NEGATION
expect(this).not.toEqual(that);
expect({}).not.toBe([]);
not11
JASMINE MATCHERS - CONTAINS
expect([1, 2, 3, 4]).toContain(3);
expect(["Jasmine", "Qunit", "Mocha", "Casper"]).toContain("Jasmine");
expect("Rychu Peja to pener").toContain("pener");
var dog = { name: "Reksio" };
expect([
{ name: "Dżeki" },
{ name: "Max" },
{ name: "Reksio" }
]).toContain(dog);
toContain12
JASMINE MATCHERS - DEFINED OR UNDEFINED
var somethingUndefined;
expect("Hello!").toBeDefined(); // success
expect(null).toBeDefined(); // success
expect(somethingUndefined).toBeDefined(); // failure
var somethingElseUndefined;
expect(somethingElseUndefined).toBeUndefined(); // success
expect(2013).toBeUndefined(); // failure
expect(null).toBeUndefined(); // failure
toBeDefined toBeUndefined13
JASMINE MATCHERS - NULLNESS
expect(null).toBeNull(); // success
expect(false).toBeNull(); // failure
expect(somethingUndefined).toBeNull(); // failure
//null is where the thing is known to exist,
//but it's not known what the value is.
toBeNull14
JASMINE MATCHERS - IS NaN
expect(5).not.toBeNaN(); // success
expect(0 / 0).toBeNaN(); // success
expect(parseInt("hello")).toBeNaN(); // success
/*
This is different from JavaScript’s built-in isNaN function. The
built-in isNaN will return true for many nonnumber types, such as
nonnumeric strings, objects, and arrays. Jasmine’s will be positive
only if it’s the NaN value.
*/
toBeNaN15
JASMINE MATCHERS - COMPARATORS
expect(8).toBeGreaterThan(5);
expect(5).toBeLessThan(12);
expect("a").toBeLessThan("z"); // it works for strings
toBeGreaterThan toBeLessThan16
JASMINE MATCHERS - NEARNESS
expect(12.34).toBeCloseTo(12.3, 1); // success
toBeCloseTo17
JASMINE MATCHERS - REGULAR EXPRESSIONS
expect("some words").toMatch(/some/); // success
toMatch18
JASMINE MATCHERS - ERROR THROWING
var errorThrower = function () {
throw new Error();
}
expect(errorThrower).toThrow(); // success
toThrow19
JASMINE - BEFORE AND AFTER TESTS
var player, wallet; // remember about scope
beforeEach( function () {
player = new Player;
});
afterEach( function () {
wallet.empty(); // empty after each test
});
beforeEach afterEach20
JASMINE MATCHERS - CUSTOM MATCHERS
beforeEach( function () {
this.addMatchers({
toBeLarge: function () {
this.message = function () {
return "Expected " + this.actual + " to be large";
};
return this.actual > 100;
}
});
});
expect(5).toBeLarge(); // failure
expect(101).toBeLarge(); // success
custom matchers21
JASMINE - NESTED SUITS
describe("Expected ", function () {
describe("something ", function () {
it("should do something", function () {
expect(2).toBe(2);
});
});
});
22
describe describe
JASMINE - SKIP THE TEST
describe("Expected ", function () {
xdescribe("something ", function () {
xit("should do something", function () {
expect(2).toBe(2);
});
return;
it("should do something else", function () {
expect(3).toBe(3);
});
});
});
23
xit xdescribe return
JASMINE - SPIES
var Dictionary = function() {},
Person = function() {};
Dictionary.prototype.hello = function () {
return "hello";
};
Dictionary.prototype.world = function () {
return "world";
};
Person.prototype.sayHelloWorld = function(dict) {
return dict.hello() + " " + dict.world();
};
var dictionary = new Dictionary,
person = new Person;
person.sayHelloWorld(dictionary); // returns "hello world"
describe("Person", function() {
it('uses the dict to say "hello world"', function() {
var dictionary = new Dictionary,
person = new Person;
// replace each function with a spy
spyOn(dictionary, "hello");
spyOn(dictionary, "world");
person.sayHelloWorld(dictionary);
// not possible without first spies
expect(dictionary.hello).toHaveBeenCalled();
expect(dictionary.world).toHaveBeenCalled();
});
});
toHaveBeenCalled
Often you test more than variable checks. Spy can pretend that he is a function or an object.
24
JASMINE - SPIES can call through the function
describe("Person", function() {
it('uses the dictionary to say "hello world"', function () {
var dictionary = new Dictionary,
person = new Person;
spyOn(person, "sayHelloWorld"); // replace hello world function with a spy
person.sayHelloWorld(dictionary);
expect(person.sayHelloWorld).toHaveBeenCalledWith(dictionary);
});
});
toHaveBeenCalledWith25
JASMINE - SPIES can return specific value
it("spy can return specific value", function () {
var dictionary = new Dictionary,
person = new Person,
result;
spyOn(dictionary, "hello").andReturn("Witaj");
result = person.sayHelloWorld(dictionary);
expect(result).toEqual("Witaj world");
});
andReturn26
JASMINE - SPIES can count its calls
callCount
it("can count calls of spy", function () {
var dictionary = new Dictionary,
spy;
spy = spyOn(dictionary, "hello");
dictionary.hello();
expect(spy.callCount).toEqual(1);
});
27
JASMINE - SPIES can get recent arguments
mostRecentCall.args
it("can give you last arguments", function () {
var dictionary = new Dictionary,
person = new Person,
spy;
spy = spyOn(person, "sayHelloWorld");
person.sayHelloWorld("No siemano");
// remember arguments will be in array
expect(spy.mostRecentCall.args).toEqual(["No siemano"]);
});
28
JASMINE - SPIES can get arguments of specific call
argsForCall[index]
it("can give you last arguments", function () {
var dictionary = new Dictionary,
person = new Person,
spy;
spy = spyOn(person, "sayHelloWorld");
person.sayHelloWorld("No siemano");
person.sayHelloWorld("Hejka");
// remember arguments will be in array and argForCall is also array
expect(spy.argsForCall[1]).toEqual(["Hejka"]);
});
29
JASMINE - SPIES can call fake functions
it("can call a fake function", function() {
var fakeHello = function() {
console.log("I’m a fake function");
return "hello";
};
var dictionary = new Dictionary();
spyOn(dictionary, "hello").andCallFake(fakeHello);
dictionary.hello(); // does an log
});
andCallFake30
JASMINE - SPIES can be created on its own
it("can have a spy function", function() {
var person = new Person();
person.getName = jasmine.createSpy("Name spy");
person.getName();
expect(person.getName).toHaveBeenCalled();
});
jasmine.createSpy31
32
JASMINE - SPIES can chain actions
person.getSecretAgentName = jasmine.createSpy("Name spy").andReturn("James Bond");
chaning
JASMINE - SPIES can be an object
it("spy can be an object", function() {
var player = jasmine.createSpyObj("player", ["hustle", "rap"]);
player.hustle();
// magic to test
});
jasmine.createSpyObj33
JASMINE - asynchronous - RUNS
it("runnin’", function() {
runs(function() {
it("should do something", function () {
expect(2).toBe(2);
});
});
});
runs34
JASMINE - asynchronous - WAITS
it('should be a test', function () {
runs(function () {
this.foo = 0;
var that = this;
setTimeout(function () {
that.foo++;
}, 250);
});
runs(function () {
expect(this.foo).toEqual(0);
});
waits(1000);
runs(function () {
expect(this.foo).toEqual(1);
});
});
waits35
36
JASMINE - asynchronous - WAITS FOR
describe('asynchronous wait for', function() {
it('should wait for something', function () {
var spreadsheet = new Spreadsheet();
waitsFor(function() {
return true; // here you can call your function which should be true
}, "Something went wrong", 3000);
runs(function () {
expect(2).toEqual(2);
});
});
});
waitsFor(function, opt message, opt timeout)
JASMINE - jQuery matchers
toBe(jQuerySelector)
toBeChecked()
toBeEmpty()
toBeHidden()
toHaveCss(css)
toBeSelected()
toBeVisible()
toContain(jQuerySelector)
toExist()
toHaveAttr(attributeName, attributeValue)
toHaveProp(propertyName, propertyValue)
toHaveBeenTriggeredOn(selector)
toHaveBeenTriggered()
toHaveBeenTriggeredOnAndWith(selector,
extraParameters)
toHaveBeenPreventedOn(selector)
toHaveBeenPrevented()
toHaveClass(className)
toHaveData(key, value)
toHaveHtml(string)
toContainHtml(string)
toContainText(string)
toHaveId(id)
toHaveText(string)
toHaveValue(value)
toHaveLength(value)
toBeDisabled()
toBeFocused()
toHandle(eventName)
toHandleWith(eventName, eventHandler)
37
JASMINE - HTML & JSON fixtures
//myfixture.html
<div id="my-fixture">some complex content here</div>
//some fixture adding
loadFixtures('myfixture.html');
$('#my-fixture').myTestedPlugin();
expect($('#my-fixture')).toBe('div');
38
JASMINE for AJAX
39
Sinon
Jasmine AJAX
Github Present for WebStorm users
40
https://github.com/thisisui/jetbrains-jasmine-test-startup
41
THANKS FOR YOUR TIME
QUESTIONS?

Jasmine - why JS tests don't smell fishy

  • 1.
    1 JASMINEWhy JavaScript testsdon’t smell fishy?
  • 2.
    WHAT I WILLTALK ABOUT: 2 • What is Unit Testing • Solution: Jasmine - whole syntax with examples • WebStorm goodies • Benefits of Units Tests • Why people don’t test JavaScript? • Test Driven Development, Behavior Driven Development
  • 3.
    WHY PEOPLE DON’TTEST JAVASCRIPT ? • project not big enough • project too complex • asynchronous XHR requests • DOM manipulation • too many browsers, platforms, devices 3
  • 4.
    UNIT TESTING isa program (test case or test specification) that isolates and tests small and specific functional unit of code. Test one small functionality per suite. Not too many things at one time. Remember! It impossible to write test for every case - try to cover every reasonable case, remember about corner cases 4 GOOD PRACTICES
  • 5.
    BENEFITS OF USINGJAVASCRIPT TESTS 5 • QA phase is cheaper - you will uncover bugs earlier • Creates great documentation • As a developer you will write better code • Shows that JS should work as was designed • Quick and easy to run - try to do it with manual testing • Runs the same every time
  • 6.
    TDD & BDD BehaviorDriven Development: agile software development technique testing from business value perspective, why some code is necessary and what its goal is 6 Test Driven Development: write tests against specification, watch your test fail, write some code, test, refactor, test-fix-implement
  • 7.
    TDD vs BDD Example:10 sorting methods TDD: one test per one method - focused on „how” each method works BDD: one test per all methods - focused on the goal give an array, sort, result 7
  • 8.
    JASMINE MATCHERS -EQUALITY expect(true).toEqual(true); expect({}).toEqual({}); 8 toEqual
  • 9.
    JASMINE MATCHERS -IDENTITY var spot = { species: "Border Collie" }; var cosmo = { species: "Border Collie" }; // success; equivalent expect(spot).toEqual(cosmo); // failure; not the same object expect(spot).toBe(cosmo); // success; the same value expect(2).toBe(2); toBe checks if two things are the same value and type, using === Primitive Types vs Reference Types primitive will be give you true, reference will give you false 9
  • 10.
    JASMINE MATCHERS -BOOLEAN expect(true).toBeTruthy(); expect(12).toBeTruthy(); expect({}).toBeTruthy(); expect(false).toBeFalsy(); expect(null).toBeFalsy(); expect("").toBeFalsy(); //false, 0, “”, undefinded, null, NaN toBeTruthy toBeFalsy10
  • 11.
    JASMINE MATCHERS -NEGATION expect(this).not.toEqual(that); expect({}).not.toBe([]); not11
  • 12.
    JASMINE MATCHERS -CONTAINS expect([1, 2, 3, 4]).toContain(3); expect(["Jasmine", "Qunit", "Mocha", "Casper"]).toContain("Jasmine"); expect("Rychu Peja to pener").toContain("pener"); var dog = { name: "Reksio" }; expect([ { name: "Dżeki" }, { name: "Max" }, { name: "Reksio" } ]).toContain(dog); toContain12
  • 13.
    JASMINE MATCHERS -DEFINED OR UNDEFINED var somethingUndefined; expect("Hello!").toBeDefined(); // success expect(null).toBeDefined(); // success expect(somethingUndefined).toBeDefined(); // failure var somethingElseUndefined; expect(somethingElseUndefined).toBeUndefined(); // success expect(2013).toBeUndefined(); // failure expect(null).toBeUndefined(); // failure toBeDefined toBeUndefined13
  • 14.
    JASMINE MATCHERS -NULLNESS expect(null).toBeNull(); // success expect(false).toBeNull(); // failure expect(somethingUndefined).toBeNull(); // failure //null is where the thing is known to exist, //but it's not known what the value is. toBeNull14
  • 15.
    JASMINE MATCHERS -IS NaN expect(5).not.toBeNaN(); // success expect(0 / 0).toBeNaN(); // success expect(parseInt("hello")).toBeNaN(); // success /* This is different from JavaScript’s built-in isNaN function. The built-in isNaN will return true for many nonnumber types, such as nonnumeric strings, objects, and arrays. Jasmine’s will be positive only if it’s the NaN value. */ toBeNaN15
  • 16.
    JASMINE MATCHERS -COMPARATORS expect(8).toBeGreaterThan(5); expect(5).toBeLessThan(12); expect("a").toBeLessThan("z"); // it works for strings toBeGreaterThan toBeLessThan16
  • 17.
    JASMINE MATCHERS -NEARNESS expect(12.34).toBeCloseTo(12.3, 1); // success toBeCloseTo17
  • 18.
    JASMINE MATCHERS -REGULAR EXPRESSIONS expect("some words").toMatch(/some/); // success toMatch18
  • 19.
    JASMINE MATCHERS -ERROR THROWING var errorThrower = function () { throw new Error(); } expect(errorThrower).toThrow(); // success toThrow19
  • 20.
    JASMINE - BEFOREAND AFTER TESTS var player, wallet; // remember about scope beforeEach( function () { player = new Player; }); afterEach( function () { wallet.empty(); // empty after each test }); beforeEach afterEach20
  • 21.
    JASMINE MATCHERS -CUSTOM MATCHERS beforeEach( function () { this.addMatchers({ toBeLarge: function () { this.message = function () { return "Expected " + this.actual + " to be large"; }; return this.actual > 100; } }); }); expect(5).toBeLarge(); // failure expect(101).toBeLarge(); // success custom matchers21
  • 22.
    JASMINE - NESTEDSUITS describe("Expected ", function () { describe("something ", function () { it("should do something", function () { expect(2).toBe(2); }); }); }); 22 describe describe
  • 23.
    JASMINE - SKIPTHE TEST describe("Expected ", function () { xdescribe("something ", function () { xit("should do something", function () { expect(2).toBe(2); }); return; it("should do something else", function () { expect(3).toBe(3); }); }); }); 23 xit xdescribe return
  • 24.
    JASMINE - SPIES varDictionary = function() {}, Person = function() {}; Dictionary.prototype.hello = function () { return "hello"; }; Dictionary.prototype.world = function () { return "world"; }; Person.prototype.sayHelloWorld = function(dict) { return dict.hello() + " " + dict.world(); }; var dictionary = new Dictionary, person = new Person; person.sayHelloWorld(dictionary); // returns "hello world" describe("Person", function() { it('uses the dict to say "hello world"', function() { var dictionary = new Dictionary, person = new Person; // replace each function with a spy spyOn(dictionary, "hello"); spyOn(dictionary, "world"); person.sayHelloWorld(dictionary); // not possible without first spies expect(dictionary.hello).toHaveBeenCalled(); expect(dictionary.world).toHaveBeenCalled(); }); }); toHaveBeenCalled Often you test more than variable checks. Spy can pretend that he is a function or an object. 24
  • 25.
    JASMINE - SPIEScan call through the function describe("Person", function() { it('uses the dictionary to say "hello world"', function () { var dictionary = new Dictionary, person = new Person; spyOn(person, "sayHelloWorld"); // replace hello world function with a spy person.sayHelloWorld(dictionary); expect(person.sayHelloWorld).toHaveBeenCalledWith(dictionary); }); }); toHaveBeenCalledWith25
  • 26.
    JASMINE - SPIEScan return specific value it("spy can return specific value", function () { var dictionary = new Dictionary, person = new Person, result; spyOn(dictionary, "hello").andReturn("Witaj"); result = person.sayHelloWorld(dictionary); expect(result).toEqual("Witaj world"); }); andReturn26
  • 27.
    JASMINE - SPIEScan count its calls callCount it("can count calls of spy", function () { var dictionary = new Dictionary, spy; spy = spyOn(dictionary, "hello"); dictionary.hello(); expect(spy.callCount).toEqual(1); }); 27
  • 28.
    JASMINE - SPIEScan get recent arguments mostRecentCall.args it("can give you last arguments", function () { var dictionary = new Dictionary, person = new Person, spy; spy = spyOn(person, "sayHelloWorld"); person.sayHelloWorld("No siemano"); // remember arguments will be in array expect(spy.mostRecentCall.args).toEqual(["No siemano"]); }); 28
  • 29.
    JASMINE - SPIEScan get arguments of specific call argsForCall[index] it("can give you last arguments", function () { var dictionary = new Dictionary, person = new Person, spy; spy = spyOn(person, "sayHelloWorld"); person.sayHelloWorld("No siemano"); person.sayHelloWorld("Hejka"); // remember arguments will be in array and argForCall is also array expect(spy.argsForCall[1]).toEqual(["Hejka"]); }); 29
  • 30.
    JASMINE - SPIEScan call fake functions it("can call a fake function", function() { var fakeHello = function() { console.log("I’m a fake function"); return "hello"; }; var dictionary = new Dictionary(); spyOn(dictionary, "hello").andCallFake(fakeHello); dictionary.hello(); // does an log }); andCallFake30
  • 31.
    JASMINE - SPIEScan be created on its own it("can have a spy function", function() { var person = new Person(); person.getName = jasmine.createSpy("Name spy"); person.getName(); expect(person.getName).toHaveBeenCalled(); }); jasmine.createSpy31
  • 32.
    32 JASMINE - SPIEScan chain actions person.getSecretAgentName = jasmine.createSpy("Name spy").andReturn("James Bond"); chaning
  • 33.
    JASMINE - SPIEScan be an object it("spy can be an object", function() { var player = jasmine.createSpyObj("player", ["hustle", "rap"]); player.hustle(); // magic to test }); jasmine.createSpyObj33
  • 34.
    JASMINE - asynchronous- RUNS it("runnin’", function() { runs(function() { it("should do something", function () { expect(2).toBe(2); }); }); }); runs34
  • 35.
    JASMINE - asynchronous- WAITS it('should be a test', function () { runs(function () { this.foo = 0; var that = this; setTimeout(function () { that.foo++; }, 250); }); runs(function () { expect(this.foo).toEqual(0); }); waits(1000); runs(function () { expect(this.foo).toEqual(1); }); }); waits35
  • 36.
    36 JASMINE - asynchronous- WAITS FOR describe('asynchronous wait for', function() { it('should wait for something', function () { var spreadsheet = new Spreadsheet(); waitsFor(function() { return true; // here you can call your function which should be true }, "Something went wrong", 3000); runs(function () { expect(2).toEqual(2); }); }); }); waitsFor(function, opt message, opt timeout)
  • 37.
    JASMINE - jQuerymatchers toBe(jQuerySelector) toBeChecked() toBeEmpty() toBeHidden() toHaveCss(css) toBeSelected() toBeVisible() toContain(jQuerySelector) toExist() toHaveAttr(attributeName, attributeValue) toHaveProp(propertyName, propertyValue) toHaveBeenTriggeredOn(selector) toHaveBeenTriggered() toHaveBeenTriggeredOnAndWith(selector, extraParameters) toHaveBeenPreventedOn(selector) toHaveBeenPrevented() toHaveClass(className) toHaveData(key, value) toHaveHtml(string) toContainHtml(string) toContainText(string) toHaveId(id) toHaveText(string) toHaveValue(value) toHaveLength(value) toBeDisabled() toBeFocused() toHandle(eventName) toHandleWith(eventName, eventHandler) 37
  • 38.
    JASMINE - HTML& JSON fixtures //myfixture.html <div id="my-fixture">some complex content here</div> //some fixture adding loadFixtures('myfixture.html'); $('#my-fixture').myTestedPlugin(); expect($('#my-fixture')).toBe('div'); 38
  • 39.
  • 40.
    Github Present forWebStorm users 40 https://github.com/thisisui/jetbrains-jasmine-test-startup
  • 41.
    41 THANKS FOR YOURTIME QUESTIONS?