Client Side Unit Testing
Upcoming SlideShare
Loading in...5
×
 

Client Side Unit Testing

on

  • 1,344 views

This slide introduces you what unit testing is, differences between functional testing and unit testing. Furthermore, it tells you the concepts about jasmine and sinon.js

This slide introduces you what unit testing is, differences between functional testing and unit testing. Furthermore, it tells you the concepts about jasmine and sinon.js

Statistics

Views

Total Views
1,344
Views on SlideShare
1,344
Embed Views
0

Actions

Likes
1
Downloads
30
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

CC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • 再开始单元测试之前, 先了解一下另外一种测试. 认清各种类型测试之间的差别\n
  • \n
  • \n
  • \n
  • 使手动测试自动化\n
  • 使手动测试自动化\n
  • 使手动测试自动化\n
  • 使手动测试自动化\n
  • \n
  • 单元测试是一种方法, 源码中独立的单元\n
  • 注意独立的单元并不一定是单独的方法, \n
  • 必须和外部依赖关系不相关, 保持单元的独立性, 是为了保证错误不是由外部的代码引起的\n不稳定的依赖关系, 如HTTP Request, timer, slow animation, 需要被stubbed\n\n
  • 必须和外部依赖关系不相关, 保持单元的独立性, 是为了保证错误不是由外部的代码引起的\n不稳定的依赖关系, 如HTTP Request, timer, slow animation, 需要被stubbed\n\n
  • 必须和外部依赖关系不相关, 保持单元的独立性, 是为了保证错误不是由外部的代码引起的\n不稳定的依赖关系, 如HTTP Request, timer, slow animation, 需要被stubbed\n\n
  • 必须和外部依赖关系不相关, 保持单元的独立性, 是为了保证错误不是由外部的代码引起的\n不稳定的依赖关系, 如HTTP Request, timer, slow animation, 需要被stubbed\n\n
  • 必须和外部依赖关系不相关, 保持单元的独立性, 是为了保证错误不是由外部的代码引起的\n不稳定的依赖关系, 如HTTP Request, timer, slow animation, 需要被stubbed\n\n
  • 单元测试又称模块测试\n
  • 单元测试又称模块测试\n
  • 单元测试又称模块测试\n
  • 单元测试又称模块测试\n
  • 单元测试又称模块测试\n
  • 这里并不是说我们不需要写functional testing. Functional Testing是一种补充, 和unit testing一样必不可缺\n我们这里只focus unit testing\n
  • 这里并不是说我们不需要写functional testing. Functional Testing是一种补充, 和unit testing一样必不可缺\n我们这里只focus unit testing\n
  • 这里并不是说我们不需要写functional testing. Functional Testing是一种补充, 和unit testing一样必不可缺\n我们这里只focus unit testing\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • 面向函数, 关注代码实现, 比较在意代码覆盖率 / 面向功能, 关注业务, 由外及内\n较专业化的测试语句 / 符合语言习惯, 接近自然语言\n
  • 面向函数, 关注代码实现, 比较在意代码覆盖率 / 面向功能, 关注业务, 由外及内\n较专业化的测试语句 / 符合语言习惯, 接近自然语言\n
  • 面向函数, 关注代码实现, 比较在意代码覆盖率 / 面向功能, 关注业务, 由外及内\n较专业化的测试语句 / 符合语言习惯, 接近自然语言\n
  • 面向函数, 关注代码实现, 比较在意代码覆盖率 / 面向功能, 关注业务, 由外及内\n较专业化的测试语句 / 符合语言习惯, 接近自然语言\n
  • 面向函数, 关注代码实现, 比较在意代码覆盖率 / 面向功能, 关注业务, 由外及内\n较专业化的测试语句 / 符合语言习惯, 接近自然语言\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Jasmine自带的Spy行为有些奇怪, spy应该设计为不阻止原始的行为\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • stub和spy的最大区别是stub后的方法, 原始行为是不会被执行的.\n
  • \n
  • \n
  • stub可以做更多, 不仅仅是返回值, 控制回调函数的参数也是可行的.\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Client Side Unit Testing Client Side Unit Testing Presentation Transcript

  • Client-Side Unit Testing Cloud Chen 2012/5/11
  • You don’t write tests
  • You know you should, but you don’t
  • You won’t be blamed for it
  • Because...
  • There are many common issues that prevent developers for writing tests
  • You think
  • You think• Tests are not necessary and irrelevant
  • You think• Tests are not necessary and irrelevant• Manual testing is enough
  • You think• Tests are not necessary and irrelevant• Manual testing is enough• Lacking of specification
  • You think• Tests are not necessary and irrelevant• Manual testing is enough• Lacking of specification• Spaghetti code are hard for testing
  • 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
  • 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
  • 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...
  • Our goal
  • Our goal Help you start writing unit testsfor front-end code
  • However,
  • Let’s start with another type of testing
  • Functional Testing
  • 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.
  • 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.
  • In a nutshell
  • In a nutshell• Written from user perspective
  • In a nutshell• Written from user perspective• Proving users are able to reproduce defined steps
  • In a nutshell• Written from user perspective• Proving users are able to reproduce defined steps• Don’t need to consider internal program structure
  • 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
  • Unit Testing
  • What is unit testingunit testing is a method by which individualunits of source code, sets of one or morecomputer program modules together withassociated control data, usage procedures, andoperating procedures, are tested to determineif they are fit for use.
  • What is unit testingunit testing is a method by which individualunits of source code, sets of one or morecomputer program modules together withassociated control data, usage procedures, andoperating procedures, are tested to determineif they are fit for use.
  • In a nutshell
  • In a nutshell• Unit testing is complete isolation
  • In a nutshell• Unit testing is complete isolation• Must irrelevant to external dependencies
  • 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
  • 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
  • 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
  • What are differences between functional and unit testing
  • What are differences between functional and unit testing unit functionalperspective 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 :)
  • What are differences between functional and unit testing unit functionalperspective 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 :)
  • What are differences between functional and unit testing unit functionalperspective 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 :)
  • What are differences between functional and unit testing unit functionalperspective 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 :)
  • What are differences between functional and unit testing unit functionalperspective 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 :)
  • Hence..
  • Hence..• We are programmer
  • Hence..• We are programmer• We care our code quality
  • Hence..• We are programmer• We care our code quality• We should write unit test code
  • Why, When ofunit testing
  • Why need unit testing
  • Why need unit testing• Ensuring every component is bug-free
  • Why need unit testing• Ensuring every component is bug-free• Ensuring every component is easy to modify
  • Why need unit testing• Ensuring every component is bug-free• Ensuring every component is easy to modify• Easy to locating bug of complex logic
  • 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
  • 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
  • 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...
  • When to do unit testing
  • When to do unit testing• When you write your own classes, modules, libraries, frameworks.
  • 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
  • testing style comparison
  • testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomaticspring from programmer stakeholder || POaccumulation test as code test as documentation
  • testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomaticspring from programmer stakeholder || POaccumulation test as code test as documentation
  • testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomaticspring from programmer stakeholder || POaccumulation test as code test as documentation
  • testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomaticspring from programmer stakeholder || POaccumulation test as code test as documentation
  • testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomaticspring from programmer stakeholder || POaccumulation test as code test as documentation
  • testing style comparison
  • testing style comparison• It doesn’t matter which style you choose
  • testing style comparison• It doesn’t matter which style you choose• It does matter how many cases you have
  • testing style comparison
  • testing style comparison As we are using Scrum,
  • testing style comparison As we are using Scrum, BDD is more suitable for us.
  • BDD Framework
  • BDD Framework• Focuses on assertion, doesn’t depend on DOM
  • BDD Framework• Focuses on assertion, doesn’t depend on DOM• Succinct API
  • BDD Framework• Focuses on assertion, doesn’t depend on DOM• Succinct API• Natively support Spy
  • BDD Framework• Focuses on assertion, doesn’t depend on DOM• Succinct API• Natively support Spy• Support spec helper
  • BDD Framework• Focuses on assertion, doesn’t depend on DOM• Succinct API• Natively support Spy• Support spec helper• Be able to extend matcher
  • 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
  • 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
  • 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
  • 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
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Typical Spec & Succinct APIdescribe("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 }); });
  • Typical Spec & Succinct APIdescribe("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
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Typical Spec & Succinct APIdescribe("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); }); });
  • Other Native Matchersexpect(x).toEqual(y);expect(x).toBe(y);expect(x).toMatch(pattern);expect(x).toBeDefined();expect(x).toBeUndefined();expect(x).toBeNull();expect(x).toBeTruthy();expect(x).toBeFalsy();expect(x).toContain(y);expect(x).toBeLessThan(y);expect(x).toBeGreaterThan(y);expect(function(){fn();}).toThrow(e);
  • Other Native Matchersexpect(x).not.toEqual(y);expect(x).not.toBe(y);expect(x).not.toMatch(pattern);expect(x).not.toBeDefined();expect(x).not.toBeUndefined();expect(x).not.toBeNull();expect(x).not.toBeTruthy();expect(x).not.toBeFalsy();expect(x).not.toContain(y);expect(x).not.toBeLessThan(y);expect(x).not.toBeGreaterThan(y);expect(function(){fn();}).not.toThrow(e);
  • Spec HelperbeforeEach(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" } } }
  • Spec HelperbeforeEach(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.
  • Spydescribe("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’); }); });});
  • Spydescribe("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’); }); });});
  • Spydescribe("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’); }); });});
  • Spydescribe("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});
  • Spydescribe("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
  • Spydescribe("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
  • Extending Matcher<a href=”#” class=”link”>this is a link</a>expect($(‘a’).hasClass(s-selected)).toBeTruthy(); expectation failed: Expected false to be truthy.
  • 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
  • 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.
  • 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’);
  • 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:
  • 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.
  • jasmine-jquery features• a set of custom matchers for jQuery framework• an API for handling HTML fixtures in your specs
  • jasmine-jquery matchersexpect(x).toBeHidden()expect(x).toBeVisible()expect(x).toHaveAttr(attributeName, attributeValue)expect(x).toHaveProp(propertyName, propertyValue)expect(x).toHaveText(string)expect(x).toHaveHtml(string)expect(x).toHaveId(id)expect(x).toBeDisabled()expect(x).toBeFocused()expect(x).toHandle(eventName)
  • jasmine-jquery fixturesIn myfixture.html file:<div id="my-fixture">some complex content here</div>Inside your test:loadFixtures(myfixture.html);$(#my-fixture).myTestedPlugin();expect($(#my-fixture)).to...;
  • Running JasmineStandalone Runner * Manually manage of your project files and specs
  • Running JasmineDedicated Server runs jasmineRuby jasmine Gem * Using yaml manages of your project files and specs
  • 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
  • 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
  • Stub every dependency
  • Stub every dependency• Native spy feature of Jasmine is not enough
  • Stub every dependency• Native spy feature of Jasmine is not enough• It doesn’t support fake timer
  • Stub every dependency• Native spy feature of Jasmine is not enough• It doesn’t support fake timer• It doesn’t support fake HTTP server
  • 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
  • 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
  • Spy/Stub/Mock
  • 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.
  • 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 stubs behavior.
  • 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 stubs 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.
  • Spy/Stub/Mock
  • Spy/Stub/Mock Spy
  • Spy/Stub/Mock Spy Stub
  • Spy/Stub/Mock Spy Stub Mock
  • Spydescribe("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(); });});
  • Spydescribe("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(); });});
  • Spydescribe("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(); });});
  • Spydescribe("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
  • Spydescribe("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
  • Stubdescribe("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); });});
  • Stubdescribe("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
  • Stubdescribe("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
  • Stubdescribe("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)
  • 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
  • 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
  • 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.
  • Mock• Mock focuses on implementation details of one method• It utilizes upfront expectation to verify details rather than asserting after the details
  • Mockdescribe("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(); });});
  • Without FakeTimersit("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(); });});
  • FakeTimersit("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();});
  • FakeTimersit("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();});
  • FakeTimersit("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();});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { Fake server stub XHR this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); responds to given URL and HTTP method it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( given HTTP method "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( given URL "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, fake response header { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); fake response body this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); immediately responds with fake data expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); });});
  • Fake Serverdescribe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require(models/Flashcard); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, { "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false } ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); verify changes }); });});
  • Next Step• fixture management • DOM • HTTP Response• Code Coverage for Javascript• Integration with CI• Integration with jsTestDriver
  • Thank you
  • Thank youif you like this topic please give me
  • Thank youif you like this topic please give me