PAINLESS JAVASCRIPT UNIT TESTING
The Presenters
@traviskaufman @benmidi
Setup
Introduction Structure Patterns Resources
Setup
Introduction Structure Patterns Resources
Setup
Introduction Patterns ResourcesStructure
Setup
Introduction Structure Patterns Resources
Introduction
Team Bicycle
● A Director of Product and Two developers
● Behavior Driven Development and Pairing
● Launched ...
Introduction
Pairing
How many times have you been here?
Introduction
Pairing
Warning: They may laugh at you.
Introduction
Team Bicycle
● A Director of Product and Two developers
● Behavior Driven Development and Pairing
● Launched ...
Introduction
Product
As close to native as possible. http://www.refinery29.com
Structure Grunt sets up tasks for running tests, example: grunt specs:debug
Gruntfile.js # Grunt for automating test runs
...
Structure Karma sets up our testing environments. Great for running tests on actual devices.
Gruntfile.js # Grunt for auto...
Structure The spec/ directory mirrors app/
Gruntfile.js # Grunt for automating test runs
karma.conf.js # Karma for unit te...
Structure Inside of spec/ is the helpers/ directory for global setup/tear down,
mocks, convenience functions and custom Ja...
Structure Our rspec acceptance tests also live in spec/
Gruntfile.js # Grunt for automating test runs
karma.conf.js # Karm...
Patterns
DRY it up
describe('views.card', function() {
var model, view;
beforeEach(function() {
model = {};
view = new Car...
Patterns
Using this
describe('views.card', function() {
beforeEach(function() {
this.model = {};
this.view = new CardView(...
Patterns
Using this
Jasmine’s userContext (aka this)
● Shared between before/afterEach hooks and tests
(including nested t...
Patterns
Lazy Eval
beforeEach(function() {
this.let_ = function(propName, getter) { // need to use "_" suffix since 'let' ...
Patterns
Lazy Eval
describe('views.Card', function() {
beforeEach(function() {
this.model = {};
this.view = new CardView(t...
Patterns
Lazy Eval
Lazy Evaluation
● Our render spec, now uses let_
● We only have to call render once, in the getter func...
Patterns
Behaves Like
describe('Email', function() {
beforeEach(function() {
this.emailAddress = 'some@example.com';
this....
Patterns
Behaves Like
Shared Behavior
● Thorough, but lots of copy-pasta code
● If API changes, all tests have to be updat...
Patterns
Behaves Like
describe('Email', function() {
beforeEach(function() {
this.emailAddress = 'some@example.com';
this....
Patterns
Behaves Like
Shared Behavior
● No more duplication of testing logic
● Extremely readable
● Leverage this along wi...
Resources
We’re Hiring
Front-End Engineer | Full Time
QA Engineer | Full Time
Developer Tools & Automation Engineer | Full...
Resources
Links
● Our Blog:
http://j.mp/R29MobileBlog
● Travis’ Gist on userContext:
http://bit.ly/BetterJasmine
● Slides:...
Upcoming SlideShare
Loading in...5
×

Painless Javascript Unit Testing

474

Published on

Some javascript testing patterns that has made writing tests at Refinery29 more maintainable. Covered in this presentation is userContext (or let) in Jasmine, lazy evaluation and shared behavior.

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
474
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
4
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Painless Javascript Unit Testing

  1. 1. PAINLESS JAVASCRIPT UNIT TESTING
  2. 2. The Presenters @traviskaufman @benmidi
  3. 3. Setup Introduction Structure Patterns Resources
  4. 4. Setup Introduction Structure Patterns Resources
  5. 5. Setup Introduction Patterns ResourcesStructure
  6. 6. Setup Introduction Structure Patterns Resources
  7. 7. Introduction Team Bicycle ● A Director of Product and Two developers ● Behavior Driven Development and Pairing ● Launched the new mobile web experience in 5 months ● 1000 Unit Tests and 200 Acceptance Tests
  8. 8. Introduction Pairing How many times have you been here?
  9. 9. Introduction Pairing Warning: They may laugh at you.
  10. 10. Introduction Team Bicycle ● A Director of Product and Two developers ● Behavior Driven Development and Pairing ● Launched the new mobile web experience in 5 months ● 1000 Unit Tests and 200 Acceptance Tests A team built for speed.
  11. 11. Introduction Product As close to native as possible. http://www.refinery29.com
  12. 12. Structure Grunt sets up tasks for running tests, example: grunt specs:debug Gruntfile.js # Grunt for automating test runs karma.conf.js # Karma for unit testing (Chrome for debugging, PhantomJS for CI builds in Jenkins) app/ | js/ | | models/ | | **/*.js | | views/ | | **/*.js spec/ | js/ | | models/ | | **/*_spec.js | | views/ | | **/*_spec.js
  13. 13. Structure Karma sets up our testing environments. Great for running tests on actual devices. Gruntfile.js # Grunt for automating test runs karma.conf.js # Karma for unit testing (Chrome for debugging, PhantomJS for CI builds in Jenkins) app/ | js/ | | models/ | | **/*.js | | views/ | | **/*.js spec/ | js/ | | models/ | | **/*_spec.js | | views/ | | **/*_spec.js
  14. 14. Structure The spec/ directory mirrors app/ Gruntfile.js # Grunt for automating test runs karma.conf.js # Karma for unit testing app/ | js/ | | models/ | | **/*.js | | views/ | | **/*.js spec/ | js/ | | models/ | | **/*_spec.js | | views/ | | **/*_spec.js
  15. 15. Structure Inside of spec/ is the helpers/ directory for global setup/tear down, mocks, convenience functions and custom Jasmine matchers. Gruntfile.js # Grunt for automating test runs karma.conf.js # Karma for unit testing app/ | js/ | | models/ | | **/*.js | | views/ | | **/*.js spec/ | helpers/ | | *.js # Helper files loaded in Karma | js/ | | models/ | | **/*_spec.js | | views/ | | **/*_spec.js
  16. 16. Structure Our rspec acceptance tests also live in spec/ Gruntfile.js # Grunt for automating test runs karma.conf.js # Karma for unit testing app/ | js/ | | models/ | | **/*.js | | views/ | | **/*.js spec/ | helpers/ | | *.js # Helper files loaded in Karma | js/ | | models/ | | **/*_spec.js | | views/ | | **/*_spec.js | features/ | | *_spec.rb | | spec_helper.rb | | support/ | | **/*.rb
  17. 17. Patterns DRY it up describe('views.card', function() { var model, view; beforeEach(function() { model = {}; view = new CardView(model); }); describe('.render', function() { beforeEach(function() { model.title = 'An Article'; view.render(); }); it('creates a "cardTitle" h3 tag set to the model's title', function() { expect(view.$el.find('.cardTitle')).toContainText(model.title); }); }); describe('when the model card type is "author_card"', function() { beforeEach(function() { model.type = 'author_card'; view.render(); }); it('adds an "authorCard" class to it's $el', function() { expect(view.$el).toHaveClass('authorCard'); }); }); });
  18. 18. Patterns Using this describe('views.card', function() { beforeEach(function() { this.model = {}; this.view = new CardView(this.model); }); describe('.render', function() { beforeEach(function() { this.model.title = 'An Article'; this.view.render(); }); it('creates a "cardTitle" h3 tag set to the model's title', function() { expect(this.view.$el.find('.cardTitle')).toContainText(this.model.title); }); }); describe('when the model card type is "author_card"', function() { beforeEach(function() { this.model.type = 'author_card'; this.view.render(); }); it('adds an "authorCard" class to it's $el', function() { expect(this.view.$el).toHaveClass('authorCard'); }); }); });
  19. 19. Patterns Using this Jasmine’s userContext (aka this) ● Shared between before/afterEach hooks and tests (including nested tests) ● Cleaned up after every test ● Removes the need for keeping track of variable declarations ● Removes problems that may occur due to scoping and/or hoisting issues ● No global leaks ● Clearer meaning within tests ● Leads the way to...
  20. 20. Patterns Lazy Eval beforeEach(function() { this.let_ = function(propName, getter) { // need to use "_" suffix since 'let' is a token in ES6 var _lazy; Object.defineProperty(this, propName, { get: function() { if (!_lazy) { _lazy = getter.call(this); } return _lazy; }, set: function() {}, enumerable: true, configurable: true }); }; }); Lazy Evaluation (a la Rspec’s let) describe('.render', function() { beforeEach(function() { this.let_('renderedView', function() { return this.view.render(); }); this.model.title = 'An Article'; }); // ...
  21. 21. Patterns Lazy Eval describe('views.Card', function() { beforeEach(function() { this.model = {}; this.view = new CardView(this.model); }); describe('.render', function() { beforeEach(function() { this.model.title = 'An Article'; this.let_('renderedView', function() { return this.view.render(); }); }); it('creates a "cardTitle" h3 tag set to the model's title', function() { expect(this.renderedView.$el.find('.cardTitle')).toContainText(this.model.title); }); describe('when the model card type is "author_card"', function() { beforeEach(function() { this.model.type = 'author_card'; // no need to re-render the view here! }); it('adds an "authorCard" class to its $el', function() { expect(this.renderedView.$el).toHaveClass('authorCard'); }); // ...
  22. 22. Patterns Lazy Eval Lazy Evaluation ● Our render spec, now uses let_ ● We only have to call render once, in the getter function, even if we change the model in nested beforeEach blocks ● Reduced code duplication ● Reduced chance of pollution due to side effects ● Smaller file sizes! ● Note: everything here can be used for Mocha as well!
  23. 23. Patterns Behaves Like describe('Email', function() { beforeEach(function() { this.emailAddress = 'some@example.com'; this.let_('email', function() { return new Email(this.emailAddress); }); }); describe('.validate', function() { describe('when emailAddress is missing the "@" symbol', function() { beforeEach(function() { this.emailAddress = 'someexample.com'; }); it('returns false', function() { expect(this.email.validate()).toBe(false); }); }); describe('when emailAddress is missing a domain after the "@" symbol', function() { beforeEach(function() { this.emailAddress = 'some@.org'; }); it('returns false', function() { expect(this.email.validate()).toBe(false); }); }); }); });
  24. 24. Patterns Behaves Like Shared Behavior ● Thorough, but lots of copy-pasta code ● If API changes, all tests have to be updated ○ e.g. what if validate changes to return a string? ● RSpec solves this with “shared examples” ● Need something like that here...
  25. 25. Patterns Behaves Like describe('Email', function() { beforeEach(function() { this.emailAddress = 'some@example.com'; this.let_('email', function() { return new Email(this.emailAddress); }); }); describe('.validate', function() { shouldInvalidate('an email without an "@" sign', 'someexample.org'); shouldInvalidate('missing a domain', 'some@.org'); //.. function shouldInvalidate(desc, example) { describe('when emailAddress is ' + desc, function() { beforeEach(function() { this.emailAddress = example; }); it('returns false', function() { expect(this.email.validate()).toBe(false); }); }); } }); });
  26. 26. Patterns Behaves Like Shared Behavior ● No more duplication of testing logic ● Extremely readable ● Leverage this along with javascripts metaprogramming techniques to create dynamically built suites ● Looks great in the console ● Completely DRY ● Huge win for something like email validation, where there are tons of cases to test ● Prevents having one test with a large amount of assertions
  27. 27. Resources We’re Hiring Front-End Engineer | Full Time QA Engineer | Full Time Developer Tools & Automation Engineer | Full Time Junior DevOps Engineer | Full Time Desktop Support Engineer | Full Time Back-End Engineer | Full Time
  28. 28. Resources Links ● Our Blog: http://j.mp/R29MobileBlog ● Travis’ Gist on userContext: http://bit.ly/BetterJasmine ● Slides: http://bit.ly/JSStackUp Thanks!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×