The document discusses client-side unit testing. It begins by noting common reasons developers give for not writing tests, such as believing tests are unnecessary or that code is too complex. It then explains the goals of introducing functional testing before unit testing, including automating manual testing and proving functionality from a user perspective without considering internal structure. The document contrasts functional and unit testing, noting unit testing focuses on complete isolation of individual units while functional testing covers most user cases. It argues that as programmers, writers should implement unit tests to ensure high quality code. The remainder covers best practices for unit testing including using a BDD framework, test organization into suites/specs/expectations, and examples of succinct testing syntax.
9. You think
• Tests are not necessary and irrelevant
10. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
11. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
• Lacking of specification
12. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
• Lacking of specification
• Spaghetti code are hard for testing
13. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
• Lacking of specification
• Spaghetti code are hard for testing
• Web application needs functional testing rather
than unit testing
14. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
• Lacking of specification
• Spaghetti code are hard for testing
• Web application needs functional testing rather
than unit testing
• Unit testing looks like only useful for back-end
code rather than front-end
15. You think
• Tests are not necessary and irrelevant
• Manual testing is enough
• Lacking of specification
• Spaghetti code are hard for testing
• Web application needs functional testing rather
than unit testing
• Unit testing looks like only useful for back-end
code rather than front-end
• Lazy...
21. What is functional testing
Functional testing is a type of black box
testing that bases its test cases on the
specifications of the software component
under test. Functions are tested by feeding
them input and examining the output, and
internal program structure is rarely
considered.
22. What is functional testing
Functional testing is a type of black box
testing that bases its test cases on the
specifications of the software component
under test. Functions are tested by feeding
them input and examining the output, and
internal program structure is rarely
considered.
25. In a nutshell
• Written from user perspective
• Proving users are able to reproduce defined steps
26. In a nutshell
• Written from user perspective
• Proving users are able to reproduce defined steps
• Don’t need to consider internal program structure
27. In a nutshell
• Written from user perspective
• Proving users are able to reproduce defined steps
• Don’t need to consider internal program structure
• Automating manual testing
29. What is unit testing
unit testing is a method by which individual
units of source code, sets of one or more
computer program modules together with
associated control data, usage procedures, and
operating procedures, are tested to determine
if they are fit for use.
30. What is unit testing
unit testing is a method by which individual
units of source code, sets of one or more
computer program modules together with
associated control data, usage procedures, and
operating procedures, are tested to determine
if they are fit for use.
33. In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
34. In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
tested unit should be stubbed
35. In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
tested unit should be stubbed
• Only the logic of that single unit is exercised
36. In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
38. What are differences between
functional and unit testing
unit functional
perspective programmer user
every module every process
goal
works as expected works as expected
all cases
coverage most user cases
even edge cases
result programmer :) user :)
39. What are differences between
functional and unit testing
unit functional
perspective programmer user
every module every process
goal
works as expected works as expected
all cases
coverage most user cases
even edge cases
result programmer :) user :)
40. What are differences between
functional and unit testing
unit functional
perspective programmer user
every module every process
goal
works as expected works as expected
all cases
coverage most user cases
even edge cases
result programmer :) user :)
41. What are differences between
functional and unit testing
unit functional
perspective programmer user
every module every process
goal
works as expected works as expected
all cases
coverage most user cases
even edge cases
result programmer :) user :)
42. What are differences between
functional and unit testing
unit functional
perspective programmer user
every module every process
goal
works as expected works as expected
all cases
coverage most user cases
even edge cases
result programmer :) user :)
49. Why need unit testing
• Ensuring every component is bug-free
50. Why need unit testing
• Ensuring every component is bug-free
• Ensuring every component is easy to modify
51. Why need unit testing
• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
52. Why need unit testing
• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
53. Why need unit testing
• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
• Testable code must be high readability and
maintainability code
54. Why need unit testing
• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
• Testable code must be high readability and
maintainability code
• and so on...
56. When to do unit testing
• When you write your own classes, modules,
libraries, frameworks.
57. When to do unit testing
• When you write your own classes, modules,
libraries, frameworks.
• You don’t need to write test code for
fundamental framework
59. testing style comparison
TDD BDD
based on function oriented feature oriented
syntax testing language idiomatic
spring from programmer stakeholder || PO
accumulation test as code test as documentation
60. testing style comparison
TDD BDD
based on function oriented feature oriented
syntax testing language idiomatic
spring from programmer stakeholder || PO
accumulation test as code test as documentation
61. testing style comparison
TDD BDD
based on function oriented feature oriented
syntax testing language idiomatic
spring from programmer stakeholder || PO
accumulation test as code test as documentation
62. testing style comparison
TDD BDD
based on function oriented feature oriented
syntax testing language idiomatic
spring from programmer stakeholder || PO
accumulation test as code test as documentation
63. testing style comparison
TDD BDD
based on function oriented feature oriented
syntax testing language idiomatic
spring from programmer stakeholder || PO
accumulation test as code test as documentation
72. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
73. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
74. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
75. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
• Be able to extend matcher
76. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
• Be able to extend matcher
• Be able to run in several environments
77. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
• Be able to extend matcher
• Be able to run in several environments
1. Browser
78. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
• Be able to extend matcher
• Be able to run in several environments
1. Browser
2. CI
79. BDD Framework
• Focuses on assertion, doesn’t depend on DOM
• Succinct API
• Natively support Spy
• Support spec helper
• Be able to extend matcher
• Be able to run in several environments
1. Browser
2. CI
3. NodeJS
80. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
81. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard'); Suite
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
82. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
afterEach(function () { Inner Suite
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
83. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
});
"IsWordIKnow": false
Spec
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
84. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url); Expectation
});
});
85. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
}); Matcher
86. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard'); Setup
});
afterEach(function () {
});
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
87. Typical Spec & Succinct API
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard'); Setup
});
afterEach(function () {
}); Tear down
describe(“when initialized”, function() {
beforeEach(function() {
this.flashcard = new this.Flashcard({
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
});
});
it(“composite key should be composed by ContentId and IsWord properties”,
function() {
var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
expect(this.flashcard.id).toEqual(expected_url);
});
});
91. Spec Helper
beforeEach(function() {
this.fixtures = {
Flashcard: {
valid: { // response starts here
"Value": "here",
"Translation": "这儿",
"Audio": "here_en.mp3",
"IsWord": true,
"ContentId": 174087,
"IsWordIKnow": false
},
error: { // response starts here
"ErrorCode": "101",
"IsSuccess": "false"
}
}
}
Using this.fixtures.Flashcard.valid to access pre-defined fixture
for testing when this spec file is included in your spec runner.
92. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’);
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
});
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
});
});
93. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’); spy on instance method
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
});
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
});
});
94. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’);
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
}); original function won’t be called
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
});
});
95. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’);
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
});
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
}); match spied function was called
});
96. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’);
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
});
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
});
}); arguments of last call
97. Spy
describe("Flashcard model", function () {
beforeEach(function () {
this.Flashcard = require('models/Flashcard');
});
describe(“when initialized”, function() {
beforeEach(function() {
spyOn(jQuery, ‘ajax’);
this.flashcard = new this.Flashcard();
this.flashcard.fetch();
});
it(“should communicate with back-end via http get method”, function() {
expect(jQuery.ajax).toHaveBeenCalled();
expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
});
});
}); arguments of last call type of first argument
98. Extending Matcher
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
99. Extending Matcher
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
jasmine-jquery comes to rescue
100. Matcher from
jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
101. Matcher from
jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
expect($(‘a’).toHaveClass(‘s-selected’);
102. Matcher from
jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
expect($(‘a’).toHaveClass(‘s-selected’);
expectation failed:
103. Matcher from
jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
expectation failed:
Expected false to be truthy.
expect($(‘a’).toHaveClass(‘s-selected’);
expectation failed:
Expected '<a></a>' to have class 's-selected'.
104. jasmine-jquery features
• a set of custom matchers for jQuery
framework
• an API for handling HTML fixtures in your
specs
109. Recap rules of Unit Testing
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
110. Recap rules of Unit Testing
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
113. Stub every dependency
• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
114. Stub every dependency
• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
115. Stub every dependency
• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
• It misuses Spy and Stub
116. Stub every dependency
• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
• It misuses Spy and Stub
Sinon.js comes to rescue
118. Spy/Stub/Mock
• A test spy is a function that records arguments, return value,
the value of this and exception thrown (if any) for all its calls.
A test spy can be an anonymous function or it can wrap an
existing function.
119. Spy/Stub/Mock
• A test spy is a function that records arguments, return value,
the value of this and exception thrown (if any) for all its calls.
A test spy can be an anonymous function or it can wrap an
existing function.
• Test stubs are functions (spies) with pre-programmed
behavior. They support the full test spy API in addition to
methods which can be used to alter the stub's behavior.
120. Spy/Stub/Mock
• A test spy is a function that records arguments, return value,
the value of this and exception thrown (if any) for all its calls.
A test spy can be an anonymous function or it can wrap an
existing function.
• Test stubs are functions (spies) with pre-programmed
behavior. They support the full test spy API in addition to
methods which can be used to alter the stub's behavior.
• Mocks (and mock expectations) are fake methods (like spies)
with pre-programmed behavior (like stubs) as well as pre-
programmed expectations. A mock will fail your test if it is not
used as expected.
125. Spy
describe("when initialized", function () {
beforeEach(function() {
sinon.spy(this, "Flashcards"); create a spy for this.Flashcards
})
afterEach(function() {
this.Flashcards.restore();
})
it("should throw Error when cultureCode is not passed", function() {
try {
var flashcards = new this.Flashcards([], {
});
} catch(e) {}
expect(this.Flashcards.calledOnce()).toBeTruthy();
expect(this.Flashcards.threw()).toBeTruthy();
expect(this.Flashcards.calledWith([], {})).toBeTruthy();
});
});
126. Spy
describe("when initialized", function () {
beforeEach(function() {
sinon.spy(this, "Flashcards");
})
afterEach(function() {
this.Flashcards.restore(); unwraps the spy
})
it("should throw Error when cultureCode is not passed", function() {
try {
var flashcards = new this.Flashcards([], {
});
} catch(e) {}
expect(this.Flashcards.calledOnce()).toBeTruthy();
expect(this.Flashcards.threw()).toBeTruthy();
expect(this.Flashcards.calledWith([], {})).toBeTruthy();
});
});
127. Spy
describe("when initialized", function () {
beforeEach(function() {
sinon.spy(this, "Flashcards");
})
afterEach(function() {
this.Flashcards.restore();
})
it("should throw Error when cultureCode is not passed", function() {
try {
var flashcards = new this.Flashcards([], {
});
} catch(e) {}
expect(this.Flashcards.calledOnce()).toBeTruthy(); spy was called once
expect(this.Flashcards.threw()).toBeTruthy();
expect(this.Flashcards.calledWith([], {})).toBeTruthy();
});
});
128. Spy
describe("when initialized", function () {
beforeEach(function() {
sinon.spy(this, "Flashcards");
})
afterEach(function() {
this.Flashcards.restore();
})
it("should throw Error when cultureCode is not passed", function() {
try {
var flashcards = new this.Flashcards([], {
});
} catch(e) {}
expect(this.Flashcards.calledOnce()).toBeTruthy();
expect(this.Flashcards.threw()).toBeTruthy();
expect(this.Flashcards.calledWith([], {})).toBeTruthy();
});
});
spy threw exception at least once
129. Spy
describe("when initialized", function () {
beforeEach(function() {
sinon.spy(this, "Flashcards");
})
afterEach(function() {
this.Flashcards.restore();
})
it("should throw Error when cultureCode is not passed", function() {
try {
var flashcards = new this.Flashcards([], {
});
} catch(e) {}
expect(this.Flashcards.calledOnce()).toBeTruthy();
expect(this.Flashcards.threw()).toBeTruthy();
expect(this.Flashcards.calledWith([], {})).toBeTruthy();
});
}); spy was called at least once with
provided argument
130. Stub
describe("when update model", function () {
beforeEach(function () {
this.sync = sinon.stub(Backbone, "sync");
});
afterEach(function () { create a stub for this.Flashcards
this.sync.restore();
});
it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
this.flashcard.set('IsWordIKnow', true);
var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');
this.flashcard.save();
expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
});
});
131. Stub
describe("when update model", function () {
beforeEach(function () {
this.sync = sinon.stub(Backbone, "sync");
});
afterEach(function () {
this.sync.restore();
});
it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
this.flashcard.set('IsWordIKnow', true);
var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');
this.flashcard.save();
expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
});
});
save method will invoke Backbone.sync that already stubbed
132. Stub
describe("when update model", function () {
beforeEach(function () {
this.sync = sinon.stub(Backbone, "sync");
});
afterEach(function () {
this.sync.restore();
});
it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
this.flashcard.set('IsWordIKnow', true);
var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');
this.flashcard.save();
expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
});
});
original sync method won’t be called if it was stubbed
So, no ajax request won’t be fired
133. Stub
describe("when update model", function () {
beforeEach(function () {
this.sync = sinon.stub(Backbone, "sync");
});
afterEach(function () {
this.sync.restore();
});
it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
this.flashcard.set('IsWordIKnow', true);
var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');
this.flashcard.save();
expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
});
});
get arguments of stubbed calling (spy can also do this)
134. Stub
But, Spy cannot do like so:
it("should always confirm every confirmation", function () {
sinon.stub(window, 'confirm');
confirm.returns(true);
expect(confirm('Are you sure?')).toBeTruthy();
window.confirm.restore();
}
Makes window.confirm() return truth
135. Stub
But, Spy cannot do like so:
it("should always confirm every confirmation", function () {
sinon.stub(window, 'confirm');
confirm.returns(true);
expect(confirm('Are you sure?')).toBeTruthy();
window.confirm.restore();
}
Native confirm behavior won’t fired
136. Stub
But, Spy cannot do like so:
it("should always confirm every confirmation", function () {
sinon.stub(window, 'confirm');
confirm.returns(true);
expect(confirm('Are you sure?')).toBeTruthy();
window.confirm.restore();
}
Native confirm behavior won’t fired
It needs user interaction to finish this test without stub.
This test probably be failed if user not confirms with it.
That test case is unstable.
137. Mock
• Mock focuses on implementation details of
one method
• It utilizes upfront expectation to verify details
rather than asserting after the details
138. Mock
describe("when initialized", function () {
it("should throw Error when cultureCode is not passed", function() {
var myAPI = { method: function () {} };
var spy = sinon.spy();
var mock = sinon.mock(myAPI);
mock.expects("method").once(); expectation upfront
myAPI.method();
spy();
verify mock behavior
mock.verify();
expect(spy.calledOnce).toBeTruthy();
});
});
139. Without FakeTimers
it("should show teacher box after 1 hour", function () {
var hour = 1000 * 60 * 60;
setTimeout(showTeacherbox, hour);
waits(hour); must wait 1 hour...crazy
runs(function() {
expect($(‘#teacherbox’)).toBeVisible();
});
});
140. FakeTimers
it("should show teacher box after 1 hour", function () {
this.clock = sinon.useFakeTimers();
var hour = 1000 * 60 * 60;
create a fake timer
setTimeout(showTeacherbox, hour);
this.clock.tick(hour);
expect($(‘#teacherbox’)).toBeVisible();
this.clock.restore();
});
141. FakeTimers
it("should show teacher box after 1 hour", function () {
this.clock = sinon.useFakeTimers();
var hour = 1000 * 60 * 60;
setTimeout(showTeacherbox, hour);
tick the clock ahead 1 hour
this.clock.tick(hour);
expect($(‘#teacherbox’)).toBeVisible();
this.clock.restore();
});
142. FakeTimers
it("should show teacher box after 1 hour", function () {
this.clock = sinon.useFakeTimers();
var hour = 1000 * 60 * 60;
setTimeout(showTeacherbox, hour);
this.clock.tick(hour - 1);
expect($(‘#teacherbox’)).toBeHidden(); won’t happen
this.clock.tick(1);
expect($(‘#teacherbox’)).toBeVisible(); will happen
this.clock.restore();
});