commit 150db8a2ba869f5fc732e58ddb28678af9acae53 Author: John Bintz Date: Tue Mar 13 13:29:42 2012 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a6fa05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.sass-cache/ + diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5b98237 --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source :rubygems + +gem 'attentive', :path => '../attentive' +gem 'rack-livereload' + +gem 'guard' +gem 'guard-livereload' + diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..4e1e0dd --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,155 @@ +PATH + remote: ../attentive + specs: + attentive (0.0.1) + backbone-rails + coffee-script + compass (~> 0.12.rc) + haml + nokogiri + pygments.rb + rack (~> 1.4.0) + rdiscount + sinatra + sprockets + sprockets-sass + sprockets-vendor_gems + thor + +GEM + remote: http://rubygems.org/ + specs: + actionmailer (3.2.1) + actionpack (= 3.2.1) + mail (~> 2.4.0) + actionpack (3.2.1) + activemodel (= 3.2.1) + activesupport (= 3.2.1) + builder (~> 3.0.0) + erubis (~> 2.7.0) + journey (~> 1.0.1) + rack (~> 1.4.0) + rack-cache (~> 1.1) + rack-test (~> 0.6.1) + sprockets (~> 2.1.2) + activemodel (3.2.1) + activesupport (= 3.2.1) + builder (~> 3.0.0) + activerecord (3.2.1) + activemodel (= 3.2.1) + activesupport (= 3.2.1) + arel (~> 3.0.0) + tzinfo (~> 0.3.29) + activeresource (3.2.1) + activemodel (= 3.2.1) + activesupport (= 3.2.1) + activesupport (3.2.1) + i18n (~> 0.6) + multi_json (~> 1.0) + addressable (2.2.6) + arel (3.0.2) + backbone-rails (0.9.0) + rails (>= 3.0.0) + blankslate (2.1.2.4) + builder (3.0.0) + chunky_png (1.2.5) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.2.0) + compass (0.12.rc.1) + chunky_png (~> 1.2) + fssm (>= 0.2.7) + sass (~> 3.1) + em-websocket (0.3.6) + addressable (>= 2.1.1) + eventmachine (>= 0.12.9) + erubis (2.7.0) + eventmachine (0.12.10) + execjs (1.3.0) + multi_json (~> 1.0) + ffi (1.0.11) + fssm (0.2.8.1) + guard (1.0.0) + ffi (>= 0.5.0) + thor (~> 0.14.6) + guard-livereload (0.4.0) + em-websocket (>= 0.2.0) + guard (>= 0.10.0) + multi_json (~> 1.0.3) + haml (3.1.4) + hike (1.2.1) + i18n (0.6.0) + journey (1.0.3) + json (1.6.5) + mail (2.4.1) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + mime-types (1.17.2) + multi_json (1.0.4) + nokogiri (1.5.0) + polyglot (0.3.3) + pygments.rb (0.2.4) + rubypython (~> 0.5.3) + rack (1.4.1) + rack-cache (1.1) + rack (>= 0.4) + rack-livereload (0.3.4) + rack + rack-protection (1.2.0) + rack + rack-ssl (1.3.2) + rack + rack-test (0.6.1) + rack (>= 1.0) + rails (3.2.1) + actionmailer (= 3.2.1) + actionpack (= 3.2.1) + activerecord (= 3.2.1) + activeresource (= 3.2.1) + activesupport (= 3.2.1) + bundler (~> 1.0) + railties (= 3.2.1) + railties (3.2.1) + actionpack (= 3.2.1) + activesupport (= 3.2.1) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (~> 0.14.6) + rake (0.9.2.2) + rdiscount (1.6.8) + rdoc (3.12) + json (~> 1.4) + rubypython (0.5.3) + blankslate (>= 2.1.2.3) + ffi (~> 1.0.7) + sass (3.1.15) + sinatra (1.3.2) + rack (~> 1.3, >= 1.3.6) + rack-protection (~> 1.2) + tilt (~> 1.3, >= 1.3.3) + sprockets (2.1.2) + hike (~> 1.2) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sprockets-sass (0.6.0) + sprockets (~> 2.0) + tilt (~> 1.1) + sprockets-vendor_gems (0.1.1) + thor (0.14.6) + tilt (1.3.3) + treetop (1.4.10) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.31) + +PLATFORMS + ruby + +DEPENDENCIES + attentive! + guard + guard-livereload + rack-livereload diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..6827062 --- /dev/null +++ b/Guardfile @@ -0,0 +1,4 @@ +guard 'livereload' do + watch(%r{^(assets|presentation)}) +end + diff --git a/assets/images/balancing-cat.jpg b/assets/images/balancing-cat.jpg new file mode 100644 index 0000000..cf6cf15 Binary files /dev/null and b/assets/images/balancing-cat.jpg differ diff --git a/assets/images/beer-cat.jpg b/assets/images/beer-cat.jpg new file mode 100644 index 0000000..30e1623 Binary files /dev/null and b/assets/images/beer-cat.jpg differ diff --git a/assets/images/cat-carrier.jpg b/assets/images/cat-carrier.jpg new file mode 100644 index 0000000..9a5351e Binary files /dev/null and b/assets/images/cat-carrier.jpg differ diff --git a/assets/images/cat-meow.jpg b/assets/images/cat-meow.jpg new file mode 100644 index 0000000..ea61bf5 Binary files /dev/null and b/assets/images/cat-meow.jpg differ diff --git a/assets/images/checklist.png b/assets/images/checklist.png new file mode 100644 index 0000000..12db52f Binary files /dev/null and b/assets/images/checklist.png differ diff --git a/assets/images/dark-side-nyan-cat.jpg b/assets/images/dark-side-nyan-cat.jpg new file mode 100644 index 0000000..2c16175 Binary files /dev/null and b/assets/images/dark-side-nyan-cat.jpg differ diff --git a/assets/images/flying-cat.jpg b/assets/images/flying-cat.jpg new file mode 100644 index 0000000..929975e Binary files /dev/null and b/assets/images/flying-cat.jpg differ diff --git a/assets/images/hungry-cat.jpg b/assets/images/hungry-cat.jpg new file mode 100644 index 0000000..09a2160 Binary files /dev/null and b/assets/images/hungry-cat.jpg differ diff --git a/assets/images/john-drinking-tea.png b/assets/images/john-drinking-tea.png new file mode 100644 index 0000000..bc25166 Binary files /dev/null and b/assets/images/john-drinking-tea.png differ diff --git a/assets/images/relevant-cat.jpg b/assets/images/relevant-cat.jpg new file mode 100644 index 0000000..6184c7e Binary files /dev/null and b/assets/images/relevant-cat.jpg differ diff --git a/assets/images/spaghetti.jpg b/assets/images/spaghetti.jpg new file mode 100644 index 0000000..9ef5e45 Binary files /dev/null and b/assets/images/spaghetti.jpg differ diff --git a/assets/images/spy-cat.jpg b/assets/images/spy-cat.jpg new file mode 100644 index 0000000..f52f252 Binary files /dev/null and b/assets/images/spy-cat.jpg differ diff --git a/assets/images/synergize.gif b/assets/images/synergize.gif new file mode 100644 index 0000000..35d42e8 Binary files /dev/null and b/assets/images/synergize.gif differ diff --git a/assets/images/synergize.jpg b/assets/images/synergize.jpg new file mode 100644 index 0000000..36e8b51 Binary files /dev/null and b/assets/images/synergize.jpg differ diff --git a/assets/images/wet-cat.jpg b/assets/images/wet-cat.jpg new file mode 100644 index 0000000..466f69b Binary files /dev/null and b/assets/images/wet-cat.jpg differ diff --git a/assets/javascripts/application.js.coffee b/assets/javascripts/application.js.coffee new file mode 100644 index 0000000..3a40d0b --- /dev/null +++ b/assets/javascripts/application.js.coffee @@ -0,0 +1,4 @@ +#= require attentive +# +Attentive.Presentation.setup('#slides') + diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss new file mode 100644 index 0000000..4dec5e4 --- /dev/null +++ b/assets/stylesheets/application.css.scss @@ -0,0 +1,43 @@ +@import 'attentive'; + +$color: #d4d2bf; + +body, html { + background-color: $color; +} + +h1, h2, h3 { + font-family: Acme, sans-serif; +} + +div.highlight { + background-color: adjust-lightness($color, -10); +} + +.style-image-80-percent { + img { + height: 80%; + } +} + +#slides { + @include transition-duration(0.3s); +} + +#intro { + width: 90%; + + @include clearfix; + position: relative; + + img { + width: 30%; + @include float-left; + } + + div { + width: 70%; + @include float-right; + } +} + diff --git a/presentation.rb b/presentation.rb new file mode 100644 index 0000000..7f4fb37 --- /dev/null +++ b/presentation.rb @@ -0,0 +1,7 @@ +require 'rack-livereload' + +Attentive.configure do |c| + c.title = "Tea Time: A Beginner's Guide to Jasmine" + c.middleware << Rack::LiveReload +end + diff --git a/presentation/01_intro.slides b/presentation/01_intro.slides new file mode 100644 index 0000000..61f6399 --- /dev/null +++ b/presentation/01_intro.slides @@ -0,0 +1,137 @@ +!SLIDE +
+ +
+

Tea Time

+

A Beginner's Guide to JavaScript Testing using Jasmine

+

By John Bintz

+
+
+ +!SLIDE +# Automated testing is important + +!SLIDE image-80-percent + + +!SLIDE +# Fortunately, we're beyond that nowadays + +!SLIDE larger +``` ruby +require 'spec_helper' + +describe MyCoolWebsite do + let(:website) { described_class.new } + + describe '#cool_method' do + subject { website.cool_method } + + let(:oh_yeah) { [ double_cool ] } + let(:double_cool) { 'double cool' } + + before do + website.stubs(:whoa_cool).returns(oh_yeah) + end + + it { should == double_cool } + end +end +``` + +!SLIDE +# But there's more to web apps than Ruby nowadays... + +!SLIDE even-larger +``` html + +``` + +!SLIDE larger +``` html + +``` + +!SLIDE +# jQuery + +!SLIDE +# Backbone + +!SLIDE +# Sprockets and RequireJS + +!SLIDE +# Automated testing is important + +!SLIDE larger +``` ruby +require 'spec_helper' + +describe MyCoolWebsite do + let(:website) { described_class.new } + + describe '#cool_method' do + subject { website.cool_method } + + let(:oh_yeah) { [ double_cool ] } + let(:double_cool) { 'double cool' } + + before do + website.stubs(:whoa_cool).returns(oh_yeah) + end + + it { should == double_cool } + end +end +``` + +!SLIDE larger +``` coffeescript +describe 'MyCoolWebsiteView', -> + website = null + + beforeEach -> + website = new MyCoolWebsiteView() + + describe '#coolMethod', -> + doubleCool = 'double cool' + ohYeah = [ doubleCool ] + + beforeEach -> + website.whoaCool = -> ohYeah + + it 'should be double cool', -> + expect(website.coolMethod()).toEqual(doubleCool) +``` + +!SLIDE +# Jasmine + +!SLIDE +# BDD unit testing framework for JavaScript + +!SLIDE +# Platform independent + +!SLIDE +# Easily extended + +!SLIDE +# Very easy to learn! + diff --git a/presentation/02_follow_along.slides b/presentation/02_follow_along.slides new file mode 100644 index 0000000..f9ee32d --- /dev/null +++ b/presentation/02_follow_along.slides @@ -0,0 +1,27 @@ +!SLIDE +# Follow along! + +!SLIDE +# [johnbintz.github.com/tea-time](http://johnbintz.github.com/tea-time/) + +!SLIDE +# No need to install anything right now + +!SLIDE +# [tryjasmine.com](http://tryjasmine.com/) + +!SLIDE +# Specs on the left + +!SLIDE +# Code under test on the right + +!SLIDE +# Write code in CoffeeScript + +!SLIDE +# Ready? + +!SLIDE +# Let's go! + diff --git a/presentation/03_describe_and_it.slides b/presentation/03_describe_and_it.slides new file mode 100644 index 0000000..1b4aea4 --- /dev/null +++ b/presentation/03_describe_and_it.slides @@ -0,0 +1,61 @@ +!SLIDE +# `describe` + +!SLIDE +# Describes a thing or a behavior of a thing + +!SLIDE +# Let's describe... + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + # cat behavior descriptions go here +``` + +!SLIDE +# Something that cats do... + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + # description of the meow behavior goes here +``` + +!SLIDE +# John behavior #1 +## Use Ruby-style indicators for instance- and class-level methods, even in Jasmine + +``` coffeescript +describe 'John', -> + describe 'spec definitions', -> + it 'should look like you did it in RSpec', -> +``` + +!SLIDE +# Describe how we expect a cat to meow + +!SLIDE +# `it` + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + it 'should meow correctly', -> + # expectation of a cat meowing +``` + +!SLIDE +# We have the description... + +!SLIDE +# Now let's add the expectations! + diff --git a/presentation/04_expect.slides b/presentation/04_expect.slides new file mode 100644 index 0000000..3040eb1 --- /dev/null +++ b/presentation/04_expect.slides @@ -0,0 +1,92 @@ +!SLIDE +# `expect` + +!SLIDE +# What should we get as an output? + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + it 'should meow correctly', -> + expect(cat.meow()).toEqual('meow') +``` + +!SLIDE +# Wait, we need a cat. + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + it 'should meow correctly', -> + cat = new Cat() + + expect(cat.meow()).toEqual('meow') +``` + +!SLIDE even-larger +``` coffeescript +# code-under-test + +class this.Cat + meow: -> +``` + +!SLIDE even-larger +``` javascript +// safety wrapper to prevent global pollution +(function() { + // ...but we want to pollute the Cat class + this.Cat = (function() { + function Cat() {} + Cat.prototype.meow = function() {}; + return Cat; + })(); +})(this) // this is window in a browser +``` + +!SLIDE +# Run it! + +!SLIDE even-larger +``` +1 spec, 1 failure + +Expected undefined to equal 'meow'. +``` + +!SLIDE +# Make it meow! + +!SLIDE even-larger +``` coffeescript +class this.Cat + meow: -> "meow" +``` + +!SLIDE even-larger +``` +1 spec, 0 failures +``` + +!SLIDE +# Here's what you should have meow... + +!SLIDE even-larger +``` coffeescript +# spec + +describe 'Cat', -> + describe '#meow', -> + it 'should meow correctly', -> + expect(cat.meow()).toEqual('meow') +``` + +!SLIDE even-larger +``` coffeescript +# code-under-test + +class this.Cat + meow: -> "meow" +``` diff --git a/presentation/05_meow_states.slides b/presentation/05_meow_states.slides new file mode 100644 index 0000000..851095d --- /dev/null +++ b/presentation/05_meow_states.slides @@ -0,0 +1,113 @@ +!SLIDE +# What if the cat meows differently based on certain states? + +!SLIDE image-80-percent + + +!SLIDE image-80-percent + + +!SLIDE +# Nested `describe` + +!SLIDE larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + describe 'hungry', -> + # Cat#meow expectation for when the cat is hungry + + describe 'going to the vet', -> + # Cat#meow expectation for when the cat knows it's vet time +``` + +!SLIDE larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + describe 'hungry', -> + it 'should be a mournful meow', -> + cat = new Cat() + cat.state = -> Cat.HUNGRY # ...just like cat.stubs(:state) + + expect(cat.meow()).toEqual("meeeyaow") + + describe 'going to the vet', -> + it 'should be an evil meow', -> + cat = new Cat() + cat.state = -> Cat.VET_PSYCHIC # ...just like the one above + + expect(cat.meow()).toEqual("raowwww") +``` + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` coffeescript +cat = new Cat() +``` + +!SLIDE even-larger +``` ruby +before do + @cat = Cat.new +end + +it 'should be a mournful meow' do + @cat.stubs(:state).returns(Cat::HUNGRY) + + @cat.meow.should == "meeyaow" +end +``` + +!SLIDE even-larger +``` ruby +before -> it -> after +``` + +!SLIDE even-larger +``` ruby +before do + @instance_variable = "yes" +end + +it "should be in the same context as the before block" do + @instance_variable.should == "yes" +end +``` + +!SLIDE even-larger +``` coffeescript +beforeEach -> it -> afterEach +``` + +!SLIDE even-larger +``` coffeescript +beforeEach -> + @instanceVariable = "yes" + +it "should be in the same context", -> + expect(@instanceVariable).toEqual("yes") +``` + +!SLIDE larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + beforeEach -> + @cat = new Cat() + + describe 'hungry', -> + it 'should be a mournful meow', -> + @cat.state = -> Cat.HUNGRY + + expect(@cat.meow()).toEqual("meeeyaow") + + describe 'going to the vet', -> + it 'should be an evil meow', -> + @cat.state = -> Cat.VET_PSYCHIC + + expect(@cat.meow()).toEqual("raowwww") +``` + diff --git a/presentation/06_context.slides b/presentation/06_context.slides new file mode 100644 index 0000000..54c2e90 --- /dev/null +++ b/presentation/06_context.slides @@ -0,0 +1,129 @@ +!SLIDE +# A little semantics game... + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + describe 'hungry', -> + # cat codes + + describe 'going to the vet', -> + # moar cat codes +``` + +!SLIDE +# This works, but it can be clearer + +!SLIDE +``` ruby +describe Cat do + describe '#meow' do + describe 'hungry' do + # cat codes + end + + describe 'going to the vet' do + # moar cat codes + end + end +end +``` + +!SLIDE +# `context` + +!SLIDE +# Description of different states for a test + +!SLIDE even-larger +``` ruby +alias :context :describe +``` + +!SLIDE larger +``` ruby +describe Cat do + let(:cat) { described_class.new } + + # save describe for things or behaviors... + describe '#meow' do + subject { cat.meow } + + # use context to describe states + context 'hungry' do + # cat codes + end + + context 'going to the vet' do + # moar cat codes + end + end +end +``` + +!SLIDE +# Jasmine doesn't have `context` + +!SLIDE +# However... + +!SLIDE even-larger +``` coffeescript +this.context = this.describe +``` + +!SLIDE even-larger +``` coffeescript +this.context = this.describe + +describe 'Cat', -> + describe '#meow', -> + context 'hungry', -> + # cat codes + + context 'going to the vet', -> + # moar cat codes +``` + +!SLIDE larger +``` coffeescript +this.context = this.describe + +describe 'Cat', -> + describe '#meow', -> + beforeEach -> + @cat = new Cat() + + context 'hungry', -> + it 'should be a mournful meow', -> + @cat.state = -> Cat.HUNGRY + + expect(@cat.meow()).toEqual("meeeyaow") + + context 'going to the vet', -> + it 'should be an evil meow', -> + @cat.state = -> Cat.VET_PSYCHIC + + expect(@cat.meow()).toEqual("raowwww") +``` + +!SLIDE even-larger +``` coffeescript +class this.Cat + @HUNGRY = 'hungry' + @VET_PSYCHIC = 'vet psychic' + + meow: -> + switch this.state() + when Cat.HUNGRY + "meeeyaow" + when Cat.VET_PSYCHIC + "raowwww" +``` + +!SLIDE even-larger +``` +2 spec, 0 failures +``` + diff --git a/presentation/07_matchers.slides b/presentation/07_matchers.slides new file mode 100644 index 0000000..a6e27d5 --- /dev/null +++ b/presentation/07_matchers.slides @@ -0,0 +1,56 @@ +!SLIDE +# Matchers + +!SLIDE even-larger +``` ruby +cat.meow.should == "meow" +cat.should be_a_kind_of(Cat) +cat.should_not be_hungry #=> cat.hungry?.should == false +``` + +!SLIDE even-larger +``` coffeescript +expect(cat.meow()).toEqual("meow") +expect(cat.prototype).toEqual(Cat.prototype) +expect(cat.isHungry()).not.toBeTruthy() +``` + +!SLIDE even-larger +# Lots of built in matchers + +``` coffeescript +toEqual(object) +toBeTruthy() +toBeFalsy() +toBeGreaterThan() +toBeLessThan() +toBeUndefined() +toContain() +toMatch() +``` + +!SLIDE even-larger +``` coffeescript +expect(cat.isHungry()).not.toBeTruthy() +``` + +!SLIDE +# Create your own matchers! + +!SLIDE even-larger +``` coffeescript +MyMatchers = + toBeHungry: -> + return @actual.isHungry() == true + +beforeEach -> + this.addMatchers(MyMatchers) + +describe 'Cat', -> + beforeEach -> + @cat = new Cat() + + it 'should not be hungry', -> + expect(@cat).not.toBeHungry() +``` + diff --git a/presentation/08_mocks_and_stubs.slides b/presentation/08_mocks_and_stubs.slides new file mode 100644 index 0000000..7c19fc1 --- /dev/null +++ b/presentation/08_mocks_and_stubs.slides @@ -0,0 +1,368 @@ +!SLIDE even-larger +``` coffeescript +describe +it +expect +toSomething() +beforeEach +afterEach +``` + +!SLIDE +# Jasmine == unit testing + +!SLIDE image-80-percent + + +!SLIDE +# No, this isn't a talk about integration testing + +!SLIDE +# Testing the *right* things in your JavaScript unit tests + +!SLIDE image-80-percent + + +!SLIDE +# John behavior #2 +## Mock, stub, and spy on anything that should be handled in an integration test + +``` coffeescript +describe 'John', -> + describe 'spec definitions', -> + it 'should keep unit tests as focused as possible', -> +``` + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` gherkin +Feature: Cat Behaviors + Scenario: Hungry cats meow a particular way + Given I have a cat + And the cat is hungry + When the cat meows + Then the meow should sound like "meeyaow" +``` + +!SLIDE even-larger +``` coffeescript +class this.Cat + @FOOD_THRESHOLD = 20 + @HUNGRY = 'hungry' + + constructor: (@foodLevel = 30) -> + + meow: -> + switch this.state() + when Cat.HUNGRY + "meeyaow" + + state: -> + if @foodLevel < Cat.FOOD_THRESHOLD + Cat.HUNGRY +``` + +!SLIDE even-larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + context 'hungry', -> + it 'should be a mournful meow', -> + cat = new Cat() + cat.foodLevel = 15 + + expect(cat.meow()).toEqual("meeeyaow") +``` + +!SLIDE +# A perfectly cromulent test + +!SLIDE larger +``` coffeescript +class this.Cat + meow: -> + switch this.state() # <= dependent code executed + when Cat.HUNGRY + "meeyaow" +``` + +!SLIDE +# Why make your unit tests fragile? + +!SLIDE larger +``` coffeescript +cat.foodLevel = 15 # do we care about food level in this test? + # all we care about is that the cat is hungry +``` + +!SLIDE larger +``` coffeescript +describe 'Cat', -> + describe '#meow', -> + describe 'hungry', -> + it 'should be a mournful meow', -> + cat = new Cat() + cat.state = -> Cat.HUNGRY # <= we don't care how state works, + # we just want a hungry cat + + expect(cat.meow()).toEqual("meeeyaow") +``` + +!SLIDE +# Instance Stubs in JavaScript +## Just replace the method on the instance + +``` coffeescript +class this.Cat + state: -> + # cat codes + +cat = new Cat() +cat.state = -> "whatever" +``` + +!SLIDE +# Stubs just return something when called + +!SLIDE +# Mocks expect to be called + +!SLIDE +# Test fails if all mocks are not called + +!SLIDE +# Jasmine blurs the line a little + +!SLIDE image-80-percent + + +!SLIDE +# Spies work like mocks, but with additional abilities + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` coffeescript +class this.Cat + vocalProcessor: (speech) => + if this.isAirborne() + this.modifyForAirborne(speech) + else + this.modifyForGround(speech) +``` + +!SLIDE larger +``` coffeescript +describe 'Cat#vocalProcessor', -> + speech = "speech" + + beforeEach -> + @cat = new Cat() + + context 'airborne', -> + beforeEach -> + spyOn(@cat, 'modifyForAirborne') + @cat.isAirborne = -> true + + it 'should be modified for flight', -> + @cat.vocalProcessor(speech) + expect(@cat.modifyForAirborne).toHaveBeenCalledWith(speech) +``` + +!SLIDE even-larger +# `spyOn` replaces a method on an instance with a spy method + +``` coffeescript +spyOn(@cat, 'modifyForAirborne') +``` + +!SLIDE +# Can return a value, run code, run the original code, or just wait to be called + +!SLIDE +# Two basic ways to make sure a spy is called + +!SLIDE even-larger +# `toHaveBeenCalledWith()` +## Called least once with the given parameters + +``` coffeescript +expect(@cat.modifyForAirborne).toHaveBeenCalledWith(speech) +``` + +!SLIDE even-larger +# `toHaveBeenCalled()` +# Just called, no parameter check + +``` coffeescript +expect(@cat.modifyForAirborne).toHaveBeenCalled() +``` + +!SLIDE even-larger +# Instance Mocks/Spies in JavaScript +## Use `spyOn`/`toHaveBeenCalled` matchers + +``` coffeescript +class this.Cat + state: -> + # cat codes + +cat = new Cat() +spyOn(cat, 'state') +expect(cat.state).toHaveBeenCalled() +``` + +!SLIDE +# `spyOn` works great with class-level stubs and mocks, too + +!SLIDE even-larger +``` coffeescript +class this.Cat + @generateFurColor: (base) -> + # magicks to make a fur color given a base + + regrowFur: (damagedHairs) -> + for follicle in damagedHairs + follicle.regrow(Cat.generateFurColor(this.baseColor)) +``` + +!SLIDE even-larger +``` coffeescript +Cat.generateFurColor = -> + "whoops i nuked this method for every other test" +``` + +!SLIDE larger +``` coffeescript +describe 'Cat#regrowFur', -> + color = 'color' + + beforeEach -> + @cat = new Cat() + @follicle = + regrow: -> + + @follicles = [ follicle ] + + spyOn(Cat, 'generateFurColor').andReturn(color) + # ^^^ original is replaced when done + spyOn(@follicle, 'regrow') + + it 'should regrow', -> + @cat.regrowFur(@follicles) + + expect(@follicle.regrow).toHaveBeenCalledWith(color) +``` + +!SLIDE even-larger +# Class Stubs in JavaScript +## Use `spyOn` to generate stubs so that the original code is replaced after the test + +``` coffeescript +class this.Cat + @injectPsychicPowers: (cat) -> + # cat codes + +spyOn(Cat, 'injectPsychicPowers').andReturn(psychicCat) +``` + +!SLIDE +# John behavior #3 +## If you have too many mocks/stubs/contexts, your code is too complex + +``` coffeescript +describe 'John', -> + describe 'spec definitions', -> + it 'should obey the Law of Demeter as much as possible', -> + it 'should not smell too funny', -> +``` + +!SLIDE smaller +``` coffeescript +describe 'Cat#fetch', -> + object = null + + context 'a mouse', -> + beforeEach -> + object = new Mouse() + + context 'fast mouse', -> + it 'should wear down the mouse', -> + # who + + context 'slow mouse', -> + it 'should deliver a present to you', -> + # cares + + context 'a ball', -> + beforeEach -> + object = new Ball() + + context 'ball is bouncing', -> + it 'should cause the cat to leap', -> + # this + + context 'ball is rolling', -> + it 'should cause the cat to slide on the floor', -> + # test + + context 'a red dot', -> + laser = null + + beforeEach -> + laser = new Laser() + + context 'laser out of batteries', -> + it 'should not activate', -> + # is + + context 'laser functioning', -> + it 'should activate, driving the cat insane', -> + # huge and unmaintainable and silly +``` + +!SLIDE +# Sometimes you just need a big blob of unit tests + +!SLIDE even-larger +``` coffeescript +# fast and focused! + +describe 'Cat#respondsTo', -> + beforeEach -> + @cat = new Cat() + + context 'successes', -> + it 'should respond', -> + for request in [ 'kitty kitty', 'pookums', 'hisshead' ] + expect(@cat.respondsTo(request)).toBeTruthy() +``` + +!SLIDE even-larger +``` gherkin +# slow and synergistic! + +Scenario Outline: Successful responsiveness + Given I have a cat + When I call it with "" + Then the cat should respond + + Examples: + | request | + | kitty kitty | + | pookums | + | hisshead | +``` + +!SLIDE image-80-percent + + +!SLIDE +# Find what works best for you and stick with it + +!SLIDE +## ...until you get sick of it, of course... + diff --git a/presentation/09_using_in_your_project.slides b/presentation/09_using_in_your_project.slides new file mode 100644 index 0000000..6b99315 --- /dev/null +++ b/presentation/09_using_in_your_project.slides @@ -0,0 +1,33 @@ +!SLIDE +# Using it in your project + +!SLIDE +# [github.com/pivotal/jasmine-gem](http://github.com/pivotal/jasmine-gem) + +!SLIDE +# Starts a Rack server for running Jasmine against your code + +!SLIDE +# Really easy to plug into an existing Rails project + +!SLIDE +# Want to make that run fast? + +!SLIDE +# Use PhantomJS or `jasmine-headless-webkit` + +!SLIDE +# Fast code running in a real browser + +!SLIDE +# Evergreen + +!SLIDE +# Jasminerice + +!SLIDE +# Node.js + +!SLIDE +# Pick your favorite! + diff --git a/presentation/10_misc_hints.slides b/presentation/10_misc_hints.slides new file mode 100644 index 0000000..a9dcdd9 --- /dev/null +++ b/presentation/10_misc_hints.slides @@ -0,0 +1,205 @@ +!SLIDE +# Some miscellaneous hints and tips + +!SLIDE +# Testing jQuery + +!SLIDE +# Mocking and stubbing `$.fn` calls + +!SLIDE larger +``` coffeescript +this.containerWaiter = -> + $('#container').addClass('wait').append('
') +``` + +!SLIDE even-larger +``` coffeescript +$.fn.makeWait = -> + $(this).addClass('wait').append('
') + this +``` + +!SLIDE even-larger +``` coffeescript +this.containerWaiter = -> + $('#container').makeWait() +``` + +!SLIDE +# `jquery-jasmine` + +!SLIDE even-larger +``` coffeescript +describe 'container', -> + beforeEach -> + setFixtures('
') + + it 'should make it wait', -> + containerWaiter() + expect($('#container')).toHaveClass('wait') + expect($('#container')).toContain('div.waiting') +``` + +!SLIDE image-80-percent + + +!SLIDE image-80-percent + + +!SLIDE even-larger +``` coffeescript +describe '$.fn.makeWait', -> + it 'should make wait', -> + $div = $('
') + $div.makeWait() + + expect($div).toHaveClass('wait') + expect($div).toContain('div.waiting') +``` + +!SLIDE even-larger +``` coffeescript +describe 'container', -> + beforeEach -> + setFixtures('
') + spyOn($.fn, 'makeWait') + + it 'should make it wait', -> + containerWaiter() + expect($.fn.makeWait).toHaveBeenCalled() +``` + +!SLIDE +# No longer testing jQuery, just testing for our code + +!SLIDE +# Animations and other time-dependent things + +!SLIDE even-larger +``` coffeescript +class Cat + constructor: -> + @mood = "happy" + + pet: -> + setTimeout( + -> @mood = "angry" + , 500 + ) +``` + +!SLIDE +# Do you really need to test the `setTimeout`? + +!SLIDE even-larger +``` coffeescript +class Cat + constructor: -> + @mood = "happy" + + pet: -> setTimeout(@makeAngry, 500) + + makeAngry: => @mood = "angry" +``` + +!SLIDE +# Use Jasmine's `waitsFor` and `runs` + +!SLIDE larger +``` coffeescript +describe 'cat moods', -> + it 'should change moods', -> + cat = new Cat() + + # we want to know the cat's current mood + currentMood = cat.mood + + # start petting the cat + runs -> cat.pet() + + # wait one second for the cat's mood to change + waitsFor( + -> + cat.mood != currentMood + , "Cat changed its mood", + 1000 + ) + + # expect the inevitable + runs -> + expect(cat.mood).toEqual('angry') +``` + +!SLIDE +# Underscore.js mixins +## and other prototype mixin-style extensions + +!SLIDE even-larger +``` coffeescript +CatLike = + catify: (name) -> + "meow meow #{name}" + +# mix in to the Underscore object +_.mixin(CatLike) + +# use it +_.catify("john") # => "meow meow john" +``` + +!SLIDE even-larger +``` coffeescript +CatLike = + catify: (name) -> "meow meow #{name}" + +class Cat + hiss: -> "hiss" + +# like Ruby include, add code to instances +for method, code of CatLike + Cat.prototype[method] = code + +cat = new Cat() +cat.catify("john") # => "meow meow #{name}" +``` + +!SLIDE even-larger +``` coffeescript +CatLike = + catify: (name) -> "meow meow #{name}" + +class Cat + hiss: -> "hiss" + +# like Ruby extend, add code to class +for method, code of CatLike + Cat[method] = code + +Cat.catify("john") # => "meow meow john" +``` + +!SLIDE even-larger +``` coffeescript +describe '_.catify', -> + it 'should catify', -> + expect(_.catify("hiss")).toEqual("meow meow hiss") +``` + +!SLIDE +# Eliminate the Underscore.js dependency + +!SLIDE even-larger +``` coffeescript +describe 'CatLike', -> + beforeEach -> + @helper = {} + + for method, code of CatLike + @helper[method] = code + + describe '#catify', -> + it 'should catify', -> + expect(@helper.catify("hiss")).toEqual("meow meow hiss") +``` + diff --git a/presentation/11_conclusion.slides b/presentation/11_conclusion.slides new file mode 100644 index 0000000..bf553e1 --- /dev/null +++ b/presentation/11_conclusion.slides @@ -0,0 +1,50 @@ +!SLIDE +# So that's pretty much it. + +!SLIDE even-larger +# Basic parts of Jasmine unit tests + +``` coffeescript +describe +it +expect +toSomething() +beforeEach +afterEach +``` + +!SLIDE even-larger +# Mocking and stubbing + +``` coffeescript +direct method replacement +spyOn() +toHaveBeenCalled() +toHaveBeenCalledWith() +``` + +!SLIDE +# Running Jasmine in your project + +!SLIDE even-larger +# Hints and tips for JavaScript testing + +``` coffeescript +waitsFor() +runs() +``` + +!SLIDE +# [Jasmine documentation](http://pivotal.github.com/jasmine/) + +!SLIDE +# [johnbintz.github.com/tea-time](http://johnbintz.github.com/tea-time/) + +!SLIDE +# Any questions? + +!SLIDE +# Thank you! +## [@johnbintz](http://twitter.com/johnbintz/) +## [GitHub](http://github.com/johnbintz/) + diff --git a/views/_header.haml b/views/_header.haml new file mode 100644 index 0000000..c9ec9bd --- /dev/null +++ b/views/_header.haml @@ -0,0 +1,2 @@ +%link{:href => 'http://fonts.googleapis.com/css?family=Acme', :rel => 'stylesheet', :type => 'text/css'}/ +