initial commit

This commit is contained in:
John Bintz 2012-03-13 13:29:42 -04:00
commit 150db8a2ba
34 changed files with 1497 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
.sass-cache/

8
Gemfile Normal file
View File

@ -0,0 +1,8 @@
source :rubygems
gem 'attentive', :path => '../attentive'
gem 'rack-livereload'
gem 'guard'
gem 'guard-livereload'

155
Gemfile.lock Normal file
View File

@ -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

4
Guardfile Normal file
View File

@ -0,0 +1,4 @@
guard 'livereload' do
watch(%r{^(assets|presentation)})
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/images/beer-cat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/images/cat-meow.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/images/checklist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
assets/images/spaghetti.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
assets/images/spy-cat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/images/synergize.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

BIN
assets/images/synergize.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
assets/images/wet-cat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,4 @@
#= require attentive
#
Attentive.Presentation.setup('#slides')

View File

@ -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;
}
}

7
presentation.rb Normal file
View File

@ -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

View File

@ -0,0 +1,137 @@
!SLIDE
<div id="intro">
<img src="assets/john-drinking-tea.png" />
<div>
<h1>Tea Time</h1>
<h2>A Beginner's Guide to JavaScript Testing using Jasmine</h2>
<h3>By John Bintz</h3>
</div>
</div>
!SLIDE
# Automated testing is important
!SLIDE image-80-percent
<img src="assets/checklist.png" />
!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
<img src="normal.gif"
onmouseover="this.src='hover.gif'"
onmouseout="this.src='normal.gif'" />
```
!SLIDE larger
``` html
<script type="text/javascript">
function showMyCoolTitle(title, length) {
if (length == null) { length = 0; }
if (length < title.length) {
document.title = title.substr(0, length);
length++;
setTimeout(function() { showMyCoolTitle(title, length); }, 75);
}
}
window.onload = function() { showMyCoolTitle("My cool website! Whoaaaaa!"); }
</script>
```
!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!

View File

@ -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!

View File

@ -0,0 +1,61 @@
!SLIDE
# `describe`
!SLIDE
# Describes a thing or a behavior of a thing
!SLIDE
# Let's describe...
!SLIDE image-80-percent
<img src="assets/relevant-cat.jpg" />
!SLIDE even-larger
``` coffeescript
describe 'Cat', ->
# cat behavior descriptions go here
```
!SLIDE
# Something that cats do...
!SLIDE image-80-percent
<img src="assets/cat-meow.jpg" />
!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!

View File

@ -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"
```

View File

@ -0,0 +1,113 @@
!SLIDE
# What if the cat meows differently based on certain states?
!SLIDE image-80-percent
<img src="assets/hungry-cat.jpg" />
!SLIDE image-80-percent
<img src="assets/cat-carrier.jpg" />
!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
<img src="assets/wet-cat.jpg" />
!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")
```

View File

@ -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
```

View File

@ -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()
```

View File

@ -0,0 +1,368 @@
!SLIDE even-larger
``` coffeescript
describe
it
expect
toSomething()
beforeEach
afterEach
```
!SLIDE
# Jasmine == unit testing
!SLIDE image-80-percent
<img src="assets/synergize.jpg" />
!SLIDE
# No, this isn't a talk about integration testing
!SLIDE
# Testing the *right* things in your JavaScript unit tests
!SLIDE image-80-percent
<img src="assets/spaghetti.jpg" />
!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
<img src="assets/beer-cat.jpg" />
!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
<img src="assets/spy-cat.jpg" />
!SLIDE
# Spies work like mocks, but with additional abilities
!SLIDE image-80-percent
<img src="assets/flying-cat.jpg" />
!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 "<request>"
Then the cat should respond
Examples:
| request |
| kitty kitty |
| pookums |
| hisshead |
```
!SLIDE image-80-percent
<img src="assets/balancing-cat.jpg" />
!SLIDE
# Find what works best for you and stick with it
!SLIDE
## ...until you get sick of it, of course...

View File

@ -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!

View File

@ -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('<div class="waiting" />')
```
!SLIDE even-larger
``` coffeescript
$.fn.makeWait = ->
$(this).addClass('wait').append('<div class="waiting" />')
this
```
!SLIDE even-larger
``` coffeescript
this.containerWaiter = ->
$('#container').makeWait()
```
!SLIDE
# `jquery-jasmine`
!SLIDE even-larger
``` coffeescript
describe 'container', ->
beforeEach ->
setFixtures('<div id="container" />')
it 'should make it wait', ->
containerWaiter()
expect($('#container')).toHaveClass('wait')
expect($('#container')).toContain('div.waiting')
```
!SLIDE image-80-percent
<img src="assets/wet-cat.jpg" />
!SLIDE image-80-percent
<img src="assets/spaghetti.jpg" />
!SLIDE even-larger
``` coffeescript
describe '$.fn.makeWait', ->
it 'should make wait', ->
$div = $('<div />')
$div.makeWait()
expect($div).toHaveClass('wait')
expect($div).toContain('div.waiting')
```
!SLIDE even-larger
``` coffeescript
describe 'container', ->
beforeEach ->
setFixtures('<div id="container" />')
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")
```

View File

@ -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/)

2
views/_header.haml Normal file
View File

@ -0,0 +1,2 @@
%link{:href => 'http://fonts.googleapis.com/css?family=Acme', :rel => 'stylesheet', :type => 'text/css'}/