SlideShare a Scribd company logo
TESTING RICH *SCRIPT
             APPLICATIONS WITH RAILS
                          @markbates




Monday, February 25, 13
Monday, February 25, 13
http://www.metacasts.tv
                             CONFOO2013



Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
Finished in 4.41041 seconds
    108 examples, 0 failures




Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
A QUICK POLL




Monday, February 25, 13
Monday, February 25, 13
app/models/todo.rb
   class Todo < ActiveRecord::Base

       validates :body, presence: true

       attr_accessible :body, :completed

   end




Monday, February 25, 13
spec/models/todo_spec.rb
   require 'spec_helper'

   describe Todo do

       it "requires a body" do
         todo = Todo.new
         todo.should_not be_valid
         todo.errors[:body].should include("can't be blank")
         todo.body = "Do something"
         todo.should be_valid
       end

   end




Monday, February 25, 13
app/controllers/todos_controller.rb
   class TodosController < ApplicationController
     respond_to :html, :json

      def index
        respond_to do |format|
          format.html {}
          format.json do
            @todos = Todo.order("created_at asc")
            respond_with @todos
          end
        end
      end

      def show
        @todo = Todo.find(params[:id])
        respond_with @todo
      end

      def create
        @todo = Todo.create(params[:todo])
        respond_with @todo
      end

      def update
        @todo = Todo.find(params[:id])
        @todo.update_attributes(params[:todo])
        respond_with @todo
      end

      def destroy
        @todo = Todo.find(params[:id])
        @todo.destroy
        respond_with @todo
      end

   end




Monday, February 25, 13
spec/controllers/todos_controller_spec.rb
   require 'spec_helper'
                                                                                      it "responds with errors" do
                                                                                          expect {
   describe TodosController do
                                                                                            post :create, todo: {}, format: 'json'

     let(:todo) { Factory(:todo) }
                                                                                            response.should_not be_successful
                                                                                            json = decode_json(response.body)
     describe 'index' do
                                                                                            json.errors.should have(1).error
                                                                                            json.errors.body.should include("can't be blank")
       context "HTML" do
                                                                                          }.to_not change(Todo, :count)
                                                                                      end
           it "renders the HTML page" do
             get :index
                                                                                    end

             response.should render_template(:index)
                                                                                end
             assigns(:todos).should be_nil
           end
                                                                                describe 'update' do

       end
                                                                                    context "JSON" do

       context "JSON" do
                                                                                      it "updates a todo" do
                                                                                        put :update, id: todo.id, todo: {body: "do something else"}, format: 'json'
           it "returns JSON for the todos" do
             get :index, format: "json"
                                                                                          response.should be_successful
                                                                                          todo.reload
             response.should_not render_template(:index)
                                                                                          todo.body.should eql "do something else"
             assigns(:todos).should_not be_nil
                                                                                      end
           end

                                                                                      it "responds with errors" do
       end
                                                                                        put :update, id: todo.id, todo: {body: ""}, format: 'json'

     end
                                                                                          response.should_not be_successful
                                                                                          json = decode_json(response.body)
     describe 'show' do
                                                                                          json.errors.should have(1).error
                                                                                        json.errors.body.should include("can't be blank")
       context "JSON" do
                                                                                      end

           it "returns the todo" do
                                                                                    end
             get :show, id: todo.id, format: 'json'

                                                                                end
             response.should be_successful
             response.body.should eql todo.to_json
                                                                                describe 'destroy' do
           end

                                                                                    context "JSON" do
       end

                                                                                      it "destroys the todo" do
     end
                                                                                          todo.should_not be_nil
                                                                                          expect {
     describe 'create' do
                                                                                            delete :destroy, id: todo.id, format: 'JSON'
                                                                                          }.to change(Todo, :count).by(-1)
       context "JSON" do
                                                                                      end

           it "creates a new todo" do
                                                                                    end
             expect {
                 post :create, todo: {body: "do something"}, format: 'json'
                                                                                end

               response.should be_successful
                                                                              end
             }.to change(Todo, :count).by(1)
           end




Monday, February 25, 13
app/views/todos/index.html.erb
   <form class='form-horizontal' id='todo_form'></form>

   <ul id='todos' class="unstyled"></ul>

   <script>
     $(function() {
        new OMG.Views.TodosApp();
     })
   </script>




Monday, February 25, 13
SO WHERE’S THE CODE?




Monday, February 25, 13
app/assets/javascripts/views/todo_view.js.coffee
        class OMG.Views.TodoView extends OMG.Views.BaseView

           tagName: 'li'
           template: JST['todos/_todo']

           events:
             'change [name=completed]': 'completedChecked'
             'click .delete': 'deleteClicked'

           initialize: ->
             @model.on "change", @render
             @render()

           render: =>
             $(@el).html(@template(todo: @model))
             if @model.get("completed") is true
               @$(".todo-body").addClass("completed")
               @$("[name=completed]").attr("checked", true)
             return @

           completedChecked: (e) =>
             @model.save(completed: $(e.target).attr("checked")?)

           deleteClicked: (e) =>
             e?.preventDefault()
             if confirm("Are you sure?")
               @model.destroy()
               $(@el).remove()


Monday, February 25, 13
HOW DO WE TEST THIS?




Monday, February 25, 13
CAPYBARA?




Monday, February 25, 13
X
                          CAPYBARA?




Monday, February 25, 13
Mocha + Chai =




Monday, February 25, 13
Mocha + Chai =




Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
JavaScript example:
   describe('panda', function(){
     it('is happy', function(){
       panda.should.be("happy")
     });
   });



                          CoffeeScript example:
   describe 'panda', ->
     it 'is happy', ->
       panda.should.be("happy")




Monday, February 25, 13
Monday, February 25, 13
EXPECT/SHOULD/ASSERT
           expect(panda).to.be('happy')
           panda.should.be("happy")
           assert.equal(panda, 'happy')

           expect(foo).to.be.true
           foo.should.be.true
           assert.isTrue(foo)

           expect(foo).to.be.null
           foo.should.be.null
           assert.isNull(foo)

           expect([]).to.be.empty
           [].should.be.empty
           assert.isEmpty([])




Monday, February 25, 13
Monday, February 25, 13
ASSERTIONS/MATCHERS
    •   to (should)       •   .ok                     •   .instanceof(constructor)

    •   be                •   .true                   •   .property(name, [value])

    •   been              •   .false                  •   .ownProperty(name)

    •   is                •   .null                   •   .length(value)

    •   that              •   .undefined               •   .match(regexp)

    •   and               •   .exist                  •   .string(string)

    •   have              •   .empty                  •   .keys(key1, [key2], [...])

    •   with              •   .equal (.eql)           •   .throw(constructor)

    •   .deep             •   .above(value)           •   .respondTo(method)

    •   .a(type)          •   .below(value)           •   .satisfy(method)

    •   .include(value)   •   .within(start, finish)   •   .closeTo(expected, delta)

Monday, February 25, 13
MOCHA/CHAI WITH RAILS

    • gem           'konacha'

    • gem           'poltergiest' (brew install phantomjs)




Monday, February 25, 13
config/initializers/konacha.rb
   if defined?(Konacha)
     require 'capybara/poltergeist'
     Konacha.configure do |config|
       config.spec_dir = "spec/javascripts"
       config.driver    = :poltergeist
     end
   end




Monday, February 25, 13
rake konacha:serve




Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
LET’S WRITE A TEST!




Monday, February 25, 13
spec/javascripts/spec_helper.coffee
   # Require the appropriate asset-pipeline files:
   #= require application

   # Any other testing specific code here...
   # Custom matchers, etc....

   # Needed for stubbing out "window" properties
   # like the confirm dialog
   Konacha.mochaOptions.ignoreLeaks = true

   beforeEach ->
     @page = $("#konacha")




Monday, February 25, 13
app/assets/javascript/greeter.js.coffee
   class @Greeter

       constructor: (@name) ->
         unless @name?
           throw new Error("You need a name!")

       greet: ->
         "Hi #{@name}"




Monday, February 25, 13
spec/javascripts/greeter_spec.coffee
   #= require spec_helper

   describe "Greeter", ->

       describe "initialize", ->

           it "raises an error if no name", ->
             expect(-> new Greeter()).to.throw("You need a name!")

       describe "greet", ->

           it "greets someone", ->
             greeter = new Greeter("Mark")
             greeter.greet().should.eql("Hi Mark")




Monday, February 25, 13
Monday, February 25, 13
NOW THE HARD STUFF




Monday, February 25, 13
Monday, February 25, 13
chai-jquery
                          https://github.com/chaijs/chai-jquery




Monday, February 25, 13
MATCHERS
    •   .attr(name[, value])       •   .selected
    •   .data(name[, value])       •   .checked
    •   .class(className)          •   .disabled
    •   .id(id)                    •   .exist
    •   .html(html)                •   .match(selector) / .be(selector)
    •   .text(text)                •   .contain(selector)
    •   .value(value)              •   .have(selector)
    •   .visible
    •   .hidden


Monday, February 25, 13
spec/javascripts/spec_helper.coffee
   # Require the appropriate asset-pipeline files:
   #= require application
   #= require_tree ./support

   # Any other testing specific code here...
   # Custom matchers, etc....

   # Needed for stubbing out "window" properties
   # like the confirm dialog
   Konacha.mochaOptions.ignoreLeaks = true

   beforeEach ->
     @page = $("#konacha")




Monday, February 25, 13
app/assets/javascripts/views/todo_view.js.coffee
   class OMG.Views.TodoView extends OMG.Views.BaseView

       tagName: 'li'
       template: JST['todos/_todo']

       events:
         'change [name=completed]': 'completedChecked'
         'click .delete': 'deleteClicked'

       initialize: ->
         @model.on "change", @render
         @render()

       render: =>
         $(@el).html(@template(todo: @model))
         if @model.get("completed") is true
           @$(".todo-body").addClass("completed")
           @$("[name=completed]").attr("checked", true)
         return @

       completedChecked: (e) =>
         @model.save(completed: $(e.target).attr("checked")?)

       deleteClicked: (e) =>
         e?.preventDefault()
         if confirm("Are you sure?")
           @model.destroy()
           $(@el).remove()


Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   #= require spec_helper

   describe "OMG.Views.TodoView", ->

       beforeEach ->
         @collection = new OMG.Collections.Todos()
         @model = new OMG.Models.Todo(id: 1, body: "Do something!", completed: false)
         @view = new OMG.Views.TodoView(model: @model, collection: @collection)
         @page.html(@view.el)




Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   describe "model bindings", ->

       it "re-renders on change", ->
         $('.todo-body').should.have.text("Do something!")
         @model.set(body: "Do something else!")
         $('.todo-body').should.have.text("Do something else!")




Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   describe "displaying of todos", ->

       it "contains the body of the todo", ->
         $('.todo-body').should.have.text("Do something!")

       it "is not marked as completed", ->
         $('[name=completed]').should.not.be.checked
         $('.todo-body').should.not.have.class("completed")

       describe "completed todos", ->

           beforeEach ->
             @model.set(completed: true)

           it "is marked as completed", ->
             $('[name=completed]').should.be.checked
             $('.todo-body').should.have.class("completed")




Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   describe "checking the completed checkbox", ->

       beforeEach ->
         $('[name=completed]').should.not.be.checked
         $('[name=completed]').click()

       it "marks it as completed", ->
         $('[name=completed]').should.be.checked
         $('.todo-body').should.have.class("completed")

   describe "unchecking the completed checkbox", ->

       beforeEach ->
         @model.set(completed: true)
         $('[name=completed]').should.be.checked
         $('[name=completed]').click()

       it "marks it as not completed", ->
         $('[name=completed]').should.not.be.checked
         $('.todo-body').should.not.have.class("completed")




Monday, February 25, 13
app/assets/javascripts/todos/todo_view.coffee
   class OMG.Views.TodoView extends OMG.Views.BaseView

       # ...

       deleteClicked: (e) =>
         e?.preventDefault()
         if confirm("Are you sure?")
           @model.destroy()
           $(@el).remove()




Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   describe "clicking the delete button", ->

       describe "if confirmed", ->

            it "will remove the todo from the @page", ->
              @page.html().should.contain($(@view.el).html())
              $(".delete").click()
              @page.html().should.not.contain($(@view.el).html())

       describe "if not confirmed", ->

            it "will not remove the todo from the @page", ->
              @page.html().should.contain($(@view.el).html())
              $(".delete").click()
              @page.html().should.contain($(@view.el).html())




Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
sinon.js
                          http://sinonjs.org/




Monday, February 25, 13
SINON.JS
    • spies
    • stubs
    • mocks
    • fake                timers
    • fake                XHR
    • fake                servers
    • more

Monday, February 25, 13
spec/javascripts/spec_helper.coffee
   # Require the appropriate asset-pipeline files:
   #= require application
   #= require support/sinon
   #= require_tree ./support

   # Any other testing specific code here...
   # Custom matchers, etc....

   # Needed for stubbing out "window" properties
   # like the confirm dialog
   Konacha.mochaOptions.ignoreLeaks = true

   beforeEach ->
     @page = $("#konacha")
     @sandbox = sinon.sandbox.create()

   afterEach ->
     @sandbox.restore()




Monday, February 25, 13
spec/javascripts/views/todos/todo_view_spec.coffee
   describe "clicking the delete button", ->

       describe "if confirmed", ->

           beforeEach ->
             @sandbox.stub(window, "confirm").returns(true)

           it "will remove the todo from the @page", ->
             @page.html().should.contain($(@view.el).html())
             $(".delete").click()
             @page.html().should.not.contain($(@view.el).html())

       describe "if not confirmed", ->

           beforeEach ->
             @sandbox.stub(window, "confirm").returns(false)

           it "will not remove the todo from the @page", ->
             @page.html().should.contain($(@view.el).html())
             $(".delete").click()
             @page.html().should.contain($(@view.el).html())




Monday, February 25, 13
WHAT ABOUT AJAX
                            REQUESTS?



Monday, February 25, 13
app/assets/javascripts/views/todos/todo_list_view.js.coffee
   class OMG.Views.TodosListView extends OMG.Views.BaseView

       el: "#todos"

       initialize: ->
         @collection.on "reset", @render
         @collection.on "add", @renderTodo
         @collection.fetch()

       render: =>
         $(@el).html("")
         @collection.forEach (todo) =>
           @renderTodo(todo)

       renderTodo: (todo) =>
         view = new OMG.Views.TodoView(model: todo, collection: @collection)
         $(@el).prepend(view.el)




Monday, February 25, 13
spec/javascripts/views/todos/todo_list_view_spec.coffee
   #= require spec_helper

   describe "OMG.Views.TodosListView", ->

       beforeEach ->
         @page.html("<ul id='todos'></ul>")
         @collection = new OMG.Collections.Todos()
         @view = new OMG.Views.TodosListView(collection: @collection)

       it "fetches the collection", ->
         @collection.should.have.length(2)

       it "renders the todos from the collection", ->
         el = $(@view.el).html()
         el.should.match(/Do something!/)
         el.should.match(/Do something else!/)

       it "renders new todos added to the collection", ->
         @collection.add(new OMG.Models.Todo(body: "Do another thing!"))
         el = $(@view.el).html()
         el.should.match(/Do another thing!/)




Monday, February 25, 13
Monday, February 25, 13
APPROACH #1
                          MOCK RESPONSES



Monday, February 25, 13
1. DEFINE TEST RESPONSE(S)




Monday, February 25, 13
spec/javascripts/support/mock_responses.coffee
   window.MockServer ?= sinon.fakeServer.create()
   MockServer.respondWith(
     "GET",
     "/todos",
     [
       200,
       { "Content-Type": "application/json" },
       '''
       [
         {"body":"Do something!","completed":false,"id":1},
         {"body":"Do something else!","completed":false,"id":2}
       ]'''
     ]
   )




Monday, February 25, 13
2. RESPOND




Monday, February 25, 13
spec/javascripts/views/todos/todo_list_view_spec.coffee
   #= require spec_helper

   describe "OMG.Views.TodosListView", ->

       beforeEach ->
         @page.html("<ul id='todos'></ul>")
         @collection = new OMG.Collections.Todos()
         @view = new OMG.Views.TodosListView(collection: @collection)



       it "fetches the collection", ->
         @collection.should.have.length(2)

       it "renders the todos from the collection", ->
         el = $(@view.el).html()
         el.should.match(/Do something!/)
         el.should.match(/Do something else!/)

       it "renders new todos added to the collection", ->
         @collection.add(new OMG.Models.Todo(body: "Do another thing!"))
         el = $(@view.el).html()
         el.should.match(/Do another thing!/)


Monday, February 25, 13
spec/javascripts/views/todos/todo_list_view_spec.coffee
   #= require spec_helper

   describe "OMG.Views.TodosListView", ->

       beforeEach ->
         @page.html("<ul id='todos'></ul>")
         @collection = new OMG.Collections.Todos()
         @view = new OMG.Views.TodosListView(collection: @collection)
         MockServer.respond()

       it "fetches the collection", ->
         @collection.should.have.length(2)

       it "renders the todos from the collection", ->
         el = $(@view.el).html()
         el.should.match(/Do something!/)
         el.should.match(/Do something else!/)

       it "renders new todos added to the collection", ->
         @collection.add(new OMG.Models.Todo(body: "Do another thing!"))
         el = $(@view.el).html()
         el.should.match(/Do another thing!/)


Monday, February 25, 13
Monday, February 25, 13
APPROACH #2
                            STUBBING



Monday, February 25, 13
spec/javascripts/views/todos/todo_list_view_spec.coffee
   #= require spec_helper

   describe "OMG.Views.TodosListView (Alt.)", ->

       beforeEach ->
         @page.html("<ul id='todos'></ul>")
         @todo1 = new OMG.Models.Todo(id: 1, body: "Do something!")
         @todo2 = new OMG.Models.Todo(id: 2, body: "Do something else!")
         @collection = new OMG.Collections.Todos()
         @sandbox.stub @collection, "fetch", =>
           @collection.add(@todo1, silent: true)
           @collection.add(@todo2, silent: true)
           @collection.trigger("reset")
         @view = new OMG.Views.TodosListView(collection: @collection)

       it "fetches the collection", ->
         @collection.should.have.length(2)

       it "renders the todos from the collection", ->
         el = $(@view.el).html()
         el.should.match(new RegExp(@todo1.get("body")))
         el.should.match(new RegExp(@todo2.get("body")))


Monday, February 25, 13
Monday, February 25, 13
Monday, February 25, 13
rake konacha:run
   .........................

   Finished in 6.77 seconds
   25 examples, 0 failures




    rake konacha:run SPEC=views/todos/todo_list_view_spec
   ...

   Finished in 5.89 seconds
   3 examples, 0 failures




Monday, February 25, 13
THANK YOU
                             @markbates
                          http://www.metacasts.tv
                             CONFOO2013


Monday, February 25, 13

More Related Content

What's hot

Why Every Tester Should Learn Ruby
Why Every Tester Should Learn RubyWhy Every Tester Should Learn Ruby
Why Every Tester Should Learn Ruby
Raimonds Simanovskis
 
Specs2
Specs2Specs2
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
Joonas Lehtinen
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
rstankov
 
Stay with React.js in 2020
Stay with React.js in 2020Stay with React.js in 2020
Stay with React.js in 2020
Jerry Liao
 
Testing Backbone applications with Jasmine
Testing Backbone applications with JasmineTesting Backbone applications with Jasmine
Testing Backbone applications with Jasmine
Leon van der Grient
 
Vbscript reference
Vbscript referenceVbscript reference
Vbscript referenceRahul Ranjan
 
Cake Php 1.2 (Ocphp)
Cake Php 1.2 (Ocphp)Cake Php 1.2 (Ocphp)
Cake Php 1.2 (Ocphp)
guest193fe1
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Imagine a world without mocks
Imagine a world without mocksImagine a world without mocks
Imagine a world without mocks
kenbot
 
SOLID PRINCIPLES
SOLID PRINCIPLESSOLID PRINCIPLES
SOLID PRINCIPLES
Luciano Queiroz
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
Fabio Collini
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
Joonas Lehtinen
 
JavaFX for Business Application Developers
JavaFX for Business Application DevelopersJavaFX for Business Application Developers
JavaFX for Business Application Developers
Michael Heinrichs
 
運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript
taobao.com
 
Vaadin7
Vaadin7Vaadin7
Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)
Natasha Murashev
 
Spring vs. Java EE QConSP 2012
Spring vs. Java EE QConSP 2012Spring vs. Java EE QConSP 2012
Spring vs. Java EE QConSP 2012
Guilherme Moreira
 
Scala in practice
Scala in practiceScala in practice
Scala in practice
andyrobinson8
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
Fabio Collini
 

What's hot (20)

Why Every Tester Should Learn Ruby
Why Every Tester Should Learn RubyWhy Every Tester Should Learn Ruby
Why Every Tester Should Learn Ruby
 
Specs2
Specs2Specs2
Specs2
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Stay with React.js in 2020
Stay with React.js in 2020Stay with React.js in 2020
Stay with React.js in 2020
 
Testing Backbone applications with Jasmine
Testing Backbone applications with JasmineTesting Backbone applications with Jasmine
Testing Backbone applications with Jasmine
 
Vbscript reference
Vbscript referenceVbscript reference
Vbscript reference
 
Cake Php 1.2 (Ocphp)
Cake Php 1.2 (Ocphp)Cake Php 1.2 (Ocphp)
Cake Php 1.2 (Ocphp)
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
 
Imagine a world without mocks
Imagine a world without mocksImagine a world without mocks
Imagine a world without mocks
 
SOLID PRINCIPLES
SOLID PRINCIPLESSOLID PRINCIPLES
SOLID PRINCIPLES
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
JavaFX for Business Application Developers
JavaFX for Business Application DevelopersJavaFX for Business Application Developers
JavaFX for Business Application Developers
 
運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript
 
Vaadin7
Vaadin7Vaadin7
Vaadin7
 
Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)Protocol-Oriented MVVM (extended edition)
Protocol-Oriented MVVM (extended edition)
 
Spring vs. Java EE QConSP 2012
Spring vs. Java EE QConSP 2012Spring vs. Java EE QConSP 2012
Spring vs. Java EE QConSP 2012
 
Scala in practice
Scala in practiceScala in practice
Scala in practice
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
 

Similar to Testing Your JavaScript & CoffeeScript

Refactoring
RefactoringRefactoring
Refactoring
Amir Barylko
 
Rails2 Pr
Rails2 PrRails2 Pr
Rails2 Pr
xibbar
 
Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bitsChris Saylor
 
Tres Gemas De Ruby
Tres Gemas De RubyTres Gemas De Ruby
Tres Gemas De Ruby
Leonardo Soto
 
RSpec
RSpecRSpec
EPiServer report generation
EPiServer report generationEPiServer report generation
EPiServer report generation
Paul Graham
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
Andrew Dupont
 
Rails-like JavaScript using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript using CoffeeScript, Backbone.js and JasmineRails-like JavaScript using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript using CoffeeScript, Backbone.js and JasmineRaimonds Simanovskis
 
Introducing Elixir and OTP at the Erlang BASH
Introducing Elixir and OTP at the Erlang BASHIntroducing Elixir and OTP at the Erlang BASH
Introducing Elixir and OTP at the Erlang BASH
devbash
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescript
David Furber
 
Why ruby
Why rubyWhy ruby
Why ruby
rstankov
 
Mulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitMulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitRebecca Murphey
 
Spock
SpockSpock
Spock
nklmish
 
2013-06-15 - Software Craftsmanship mit JavaScript
2013-06-15 - Software Craftsmanship mit JavaScript2013-06-15 - Software Craftsmanship mit JavaScript
2013-06-15 - Software Craftsmanship mit JavaScript
Johannes Hoppe
 
2013-06-24 - Software Craftsmanship with JavaScript
2013-06-24 - Software Craftsmanship with JavaScript2013-06-24 - Software Craftsmanship with JavaScript
2013-06-24 - Software Craftsmanship with JavaScript
Johannes Hoppe
 
Rails workshop for Java people (September 2015)
Rails workshop for Java people (September 2015)Rails workshop for Java people (September 2015)
Rails workshop for Java people (September 2015)
Andre Foeken
 
Intro to Scala.js - Scala UG Cologne
Intro to Scala.js - Scala UG CologneIntro to Scala.js - Scala UG Cologne
Intro to Scala.js - Scala UG Cologne
Marius Soutier
 
Javascript basics
Javascript basicsJavascript basics
Javascript basics
Fin Chen
 
Introduction to Kotlin
Introduction to KotlinIntroduction to Kotlin
Introduction to Kotlin
Patrick Yin
 

Similar to Testing Your JavaScript & CoffeeScript (20)

Refactoring
RefactoringRefactoring
Refactoring
 
Rails2 Pr
Rails2 PrRails2 Pr
Rails2 Pr
 
Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bits
 
Tres Gemas De Ruby
Tres Gemas De RubyTres Gemas De Ruby
Tres Gemas De Ruby
 
RSpec
RSpecRSpec
RSpec
 
EPiServer report generation
EPiServer report generationEPiServer report generation
EPiServer report generation
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 
Rails-like JavaScript using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript using CoffeeScript, Backbone.js and JasmineRails-like JavaScript using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript using CoffeeScript, Backbone.js and Jasmine
 
Introducing Elixir and OTP at the Erlang BASH
Introducing Elixir and OTP at the Erlang BASHIntroducing Elixir and OTP at the Erlang BASH
Introducing Elixir and OTP at the Erlang BASH
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescript
 
Why ruby
Why rubyWhy ruby
Why ruby
 
Mulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitMulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development Toolkit
 
Spock
SpockSpock
Spock
 
2013-06-15 - Software Craftsmanship mit JavaScript
2013-06-15 - Software Craftsmanship mit JavaScript2013-06-15 - Software Craftsmanship mit JavaScript
2013-06-15 - Software Craftsmanship mit JavaScript
 
2013-06-24 - Software Craftsmanship with JavaScript
2013-06-24 - Software Craftsmanship with JavaScript2013-06-24 - Software Craftsmanship with JavaScript
2013-06-24 - Software Craftsmanship with JavaScript
 
Rails workshop for Java people (September 2015)
Rails workshop for Java people (September 2015)Rails workshop for Java people (September 2015)
Rails workshop for Java people (September 2015)
 
Intro to Scala.js - Scala UG Cologne
Intro to Scala.js - Scala UG CologneIntro to Scala.js - Scala UG Cologne
Intro to Scala.js - Scala UG Cologne
 
Javascript basics
Javascript basicsJavascript basics
Javascript basics
 
Introduction to Kotlin
Introduction to KotlinIntroduction to Kotlin
Introduction to Kotlin
 
Little Big Ruby
Little Big RubyLittle Big Ruby
Little Big Ruby
 

More from Mark

Building Go Web Apps
Building Go Web AppsBuilding Go Web Apps
Building Go Web Apps
Mark
 
Angular.js Fundamentals
Angular.js FundamentalsAngular.js Fundamentals
Angular.js Fundamentals
Mark
 
Go(lang) for the Rubyist
Go(lang) for the RubyistGo(lang) for the Rubyist
Go(lang) for the Rubyist
Mark
 
Mangling Ruby with TracePoint
Mangling Ruby with TracePointMangling Ruby with TracePoint
Mangling Ruby with TracePoint
Mark
 
AngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.jsAngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.js
Mark
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
Mark
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
Mark
 
GET /better
GET /betterGET /better
GET /better
Mark
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
Mark
 
Building an API in Rails without Realizing It
Building an API in Rails without Realizing ItBuilding an API in Rails without Realizing It
Building an API in Rails without Realizing It
Mark
 
5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(
Mark
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
Mark
 
RubyMotion
RubyMotionRubyMotion
RubyMotion
Mark
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
Mark
 
DRb and Rinda
DRb and RindaDRb and Rinda
DRb and Rinda
Mark
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
Mark
 
Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010
Mark
 

More from Mark (17)

Building Go Web Apps
Building Go Web AppsBuilding Go Web Apps
Building Go Web Apps
 
Angular.js Fundamentals
Angular.js FundamentalsAngular.js Fundamentals
Angular.js Fundamentals
 
Go(lang) for the Rubyist
Go(lang) for the RubyistGo(lang) for the Rubyist
Go(lang) for the Rubyist
 
Mangling Ruby with TracePoint
Mangling Ruby with TracePointMangling Ruby with TracePoint
Mangling Ruby with TracePoint
 
AngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.jsAngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.js
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
 
GET /better
GET /betterGET /better
GET /better
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
 
Building an API in Rails without Realizing It
Building an API in Rails without Realizing ItBuilding an API in Rails without Realizing It
Building an API in Rails without Realizing It
 
5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
 
RubyMotion
RubyMotionRubyMotion
RubyMotion
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
 
DRb and Rinda
DRb and RindaDRb and Rinda
DRb and Rinda
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010
 

Recently uploaded

To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
Paul Groth
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*
Frank van Harmelen
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
DianaGray10
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Inflectra
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Tobias Schneck
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Product School
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
DianaGray10
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
DianaGray10
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
KatiaHIMEUR1
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
Cheryl Hung
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
Product School
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
Product School
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
Product School
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Jeffrey Haguewood
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
91mobiles
 
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
Ramesh Iyer
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Albert Hoitingh
 

Recently uploaded (20)

To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
Builder.ai Founder Sachin Dev Duggal's Strategic Approach to Create an Innova...
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
 

Testing Your JavaScript & CoffeeScript

  • 1. TESTING RICH *SCRIPT APPLICATIONS WITH RAILS @markbates Monday, February 25, 13
  • 3. http://www.metacasts.tv CONFOO2013 Monday, February 25, 13
  • 7. Finished in 4.41041 seconds 108 examples, 0 failures Monday, February 25, 13
  • 10. A QUICK POLL Monday, February 25, 13
  • 12. app/models/todo.rb class Todo < ActiveRecord::Base validates :body, presence: true attr_accessible :body, :completed end Monday, February 25, 13
  • 13. spec/models/todo_spec.rb require 'spec_helper' describe Todo do it "requires a body" do todo = Todo.new todo.should_not be_valid todo.errors[:body].should include("can't be blank") todo.body = "Do something" todo.should be_valid end end Monday, February 25, 13
  • 14. app/controllers/todos_controller.rb class TodosController < ApplicationController respond_to :html, :json def index respond_to do |format| format.html {} format.json do @todos = Todo.order("created_at asc") respond_with @todos end end end def show @todo = Todo.find(params[:id]) respond_with @todo end def create @todo = Todo.create(params[:todo]) respond_with @todo end def update @todo = Todo.find(params[:id]) @todo.update_attributes(params[:todo]) respond_with @todo end def destroy @todo = Todo.find(params[:id]) @todo.destroy respond_with @todo end end Monday, February 25, 13
  • 15. spec/controllers/todos_controller_spec.rb require 'spec_helper' it "responds with errors" do expect { describe TodosController do post :create, todo: {}, format: 'json' let(:todo) { Factory(:todo) } response.should_not be_successful json = decode_json(response.body) describe 'index' do json.errors.should have(1).error json.errors.body.should include("can't be blank") context "HTML" do }.to_not change(Todo, :count) end it "renders the HTML page" do get :index end response.should render_template(:index) end assigns(:todos).should be_nil end describe 'update' do end context "JSON" do context "JSON" do it "updates a todo" do put :update, id: todo.id, todo: {body: "do something else"}, format: 'json' it "returns JSON for the todos" do get :index, format: "json" response.should be_successful todo.reload response.should_not render_template(:index) todo.body.should eql "do something else" assigns(:todos).should_not be_nil end end it "responds with errors" do end put :update, id: todo.id, todo: {body: ""}, format: 'json' end response.should_not be_successful json = decode_json(response.body) describe 'show' do json.errors.should have(1).error json.errors.body.should include("can't be blank") context "JSON" do end it "returns the todo" do end get :show, id: todo.id, format: 'json' end response.should be_successful response.body.should eql todo.to_json describe 'destroy' do end context "JSON" do end it "destroys the todo" do end todo.should_not be_nil expect { describe 'create' do delete :destroy, id: todo.id, format: 'JSON' }.to change(Todo, :count).by(-1) context "JSON" do end it "creates a new todo" do end expect { post :create, todo: {body: "do something"}, format: 'json' end response.should be_successful end }.to change(Todo, :count).by(1) end Monday, February 25, 13
  • 16. app/views/todos/index.html.erb <form class='form-horizontal' id='todo_form'></form> <ul id='todos' class="unstyled"></ul> <script> $(function() { new OMG.Views.TodosApp(); }) </script> Monday, February 25, 13
  • 17. SO WHERE’S THE CODE? Monday, February 25, 13
  • 18. app/assets/javascripts/views/todo_view.js.coffee class OMG.Views.TodoView extends OMG.Views.BaseView tagName: 'li' template: JST['todos/_todo'] events: 'change [name=completed]': 'completedChecked' 'click .delete': 'deleteClicked' initialize: -> @model.on "change", @render @render() render: => $(@el).html(@template(todo: @model)) if @model.get("completed") is true @$(".todo-body").addClass("completed") @$("[name=completed]").attr("checked", true) return @ completedChecked: (e) => @model.save(completed: $(e.target).attr("checked")?) deleteClicked: (e) => e?.preventDefault() if confirm("Are you sure?") @model.destroy() $(@el).remove() Monday, February 25, 13
  • 19. HOW DO WE TEST THIS? Monday, February 25, 13
  • 21. X CAPYBARA? Monday, February 25, 13
  • 22. Mocha + Chai = Monday, February 25, 13
  • 23. Mocha + Chai = Monday, February 25, 13
  • 26. JavaScript example: describe('panda', function(){ it('is happy', function(){ panda.should.be("happy") }); }); CoffeeScript example: describe 'panda', -> it 'is happy', -> panda.should.be("happy") Monday, February 25, 13
  • 28. EXPECT/SHOULD/ASSERT expect(panda).to.be('happy') panda.should.be("happy") assert.equal(panda, 'happy') expect(foo).to.be.true foo.should.be.true assert.isTrue(foo) expect(foo).to.be.null foo.should.be.null assert.isNull(foo) expect([]).to.be.empty [].should.be.empty assert.isEmpty([]) Monday, February 25, 13
  • 30. ASSERTIONS/MATCHERS • to (should) • .ok • .instanceof(constructor) • be • .true • .property(name, [value]) • been • .false • .ownProperty(name) • is • .null • .length(value) • that • .undefined • .match(regexp) • and • .exist • .string(string) • have • .empty • .keys(key1, [key2], [...]) • with • .equal (.eql) • .throw(constructor) • .deep • .above(value) • .respondTo(method) • .a(type) • .below(value) • .satisfy(method) • .include(value) • .within(start, finish) • .closeTo(expected, delta) Monday, February 25, 13
  • 31. MOCHA/CHAI WITH RAILS • gem 'konacha' • gem 'poltergiest' (brew install phantomjs) Monday, February 25, 13
  • 32. config/initializers/konacha.rb if defined?(Konacha) require 'capybara/poltergeist' Konacha.configure do |config| config.spec_dir = "spec/javascripts" config.driver = :poltergeist end end Monday, February 25, 13
  • 36. LET’S WRITE A TEST! Monday, February 25, 13
  • 37. spec/javascripts/spec_helper.coffee # Require the appropriate asset-pipeline files: #= require application # Any other testing specific code here... # Custom matchers, etc.... # Needed for stubbing out "window" properties # like the confirm dialog Konacha.mochaOptions.ignoreLeaks = true beforeEach -> @page = $("#konacha") Monday, February 25, 13
  • 38. app/assets/javascript/greeter.js.coffee class @Greeter constructor: (@name) -> unless @name? throw new Error("You need a name!") greet: -> "Hi #{@name}" Monday, February 25, 13
  • 39. spec/javascripts/greeter_spec.coffee #= require spec_helper describe "Greeter", -> describe "initialize", -> it "raises an error if no name", -> expect(-> new Greeter()).to.throw("You need a name!") describe "greet", -> it "greets someone", -> greeter = new Greeter("Mark") greeter.greet().should.eql("Hi Mark") Monday, February 25, 13
  • 41. NOW THE HARD STUFF Monday, February 25, 13
  • 43. chai-jquery https://github.com/chaijs/chai-jquery Monday, February 25, 13
  • 44. MATCHERS • .attr(name[, value]) • .selected • .data(name[, value]) • .checked • .class(className) • .disabled • .id(id) • .exist • .html(html) • .match(selector) / .be(selector) • .text(text) • .contain(selector) • .value(value) • .have(selector) • .visible • .hidden Monday, February 25, 13
  • 45. spec/javascripts/spec_helper.coffee # Require the appropriate asset-pipeline files: #= require application #= require_tree ./support # Any other testing specific code here... # Custom matchers, etc.... # Needed for stubbing out "window" properties # like the confirm dialog Konacha.mochaOptions.ignoreLeaks = true beforeEach -> @page = $("#konacha") Monday, February 25, 13
  • 46. app/assets/javascripts/views/todo_view.js.coffee class OMG.Views.TodoView extends OMG.Views.BaseView tagName: 'li' template: JST['todos/_todo'] events: 'change [name=completed]': 'completedChecked' 'click .delete': 'deleteClicked' initialize: -> @model.on "change", @render @render() render: => $(@el).html(@template(todo: @model)) if @model.get("completed") is true @$(".todo-body").addClass("completed") @$("[name=completed]").attr("checked", true) return @ completedChecked: (e) => @model.save(completed: $(e.target).attr("checked")?) deleteClicked: (e) => e?.preventDefault() if confirm("Are you sure?") @model.destroy() $(@el).remove() Monday, February 25, 13
  • 47. spec/javascripts/views/todos/todo_view_spec.coffee #= require spec_helper describe "OMG.Views.TodoView", -> beforeEach -> @collection = new OMG.Collections.Todos() @model = new OMG.Models.Todo(id: 1, body: "Do something!", completed: false) @view = new OMG.Views.TodoView(model: @model, collection: @collection) @page.html(@view.el) Monday, February 25, 13
  • 48. spec/javascripts/views/todos/todo_view_spec.coffee describe "model bindings", -> it "re-renders on change", -> $('.todo-body').should.have.text("Do something!") @model.set(body: "Do something else!") $('.todo-body').should.have.text("Do something else!") Monday, February 25, 13
  • 49. spec/javascripts/views/todos/todo_view_spec.coffee describe "displaying of todos", -> it "contains the body of the todo", -> $('.todo-body').should.have.text("Do something!") it "is not marked as completed", -> $('[name=completed]').should.not.be.checked $('.todo-body').should.not.have.class("completed") describe "completed todos", -> beforeEach -> @model.set(completed: true) it "is marked as completed", -> $('[name=completed]').should.be.checked $('.todo-body').should.have.class("completed") Monday, February 25, 13
  • 50. spec/javascripts/views/todos/todo_view_spec.coffee describe "checking the completed checkbox", -> beforeEach -> $('[name=completed]').should.not.be.checked $('[name=completed]').click() it "marks it as completed", -> $('[name=completed]').should.be.checked $('.todo-body').should.have.class("completed") describe "unchecking the completed checkbox", -> beforeEach -> @model.set(completed: true) $('[name=completed]').should.be.checked $('[name=completed]').click() it "marks it as not completed", -> $('[name=completed]').should.not.be.checked $('.todo-body').should.not.have.class("completed") Monday, February 25, 13
  • 51. app/assets/javascripts/todos/todo_view.coffee class OMG.Views.TodoView extends OMG.Views.BaseView # ... deleteClicked: (e) => e?.preventDefault() if confirm("Are you sure?") @model.destroy() $(@el).remove() Monday, February 25, 13
  • 52. spec/javascripts/views/todos/todo_view_spec.coffee describe "clicking the delete button", -> describe "if confirmed", -> it "will remove the todo from the @page", -> @page.html().should.contain($(@view.el).html()) $(".delete").click() @page.html().should.not.contain($(@view.el).html()) describe "if not confirmed", -> it "will not remove the todo from the @page", -> @page.html().should.contain($(@view.el).html()) $(".delete").click() @page.html().should.contain($(@view.el).html()) Monday, February 25, 13
  • 55. sinon.js http://sinonjs.org/ Monday, February 25, 13
  • 56. SINON.JS • spies • stubs • mocks • fake timers • fake XHR • fake servers • more Monday, February 25, 13
  • 57. spec/javascripts/spec_helper.coffee # Require the appropriate asset-pipeline files: #= require application #= require support/sinon #= require_tree ./support # Any other testing specific code here... # Custom matchers, etc.... # Needed for stubbing out "window" properties # like the confirm dialog Konacha.mochaOptions.ignoreLeaks = true beforeEach -> @page = $("#konacha") @sandbox = sinon.sandbox.create() afterEach -> @sandbox.restore() Monday, February 25, 13
  • 58. spec/javascripts/views/todos/todo_view_spec.coffee describe "clicking the delete button", -> describe "if confirmed", -> beforeEach -> @sandbox.stub(window, "confirm").returns(true) it "will remove the todo from the @page", -> @page.html().should.contain($(@view.el).html()) $(".delete").click() @page.html().should.not.contain($(@view.el).html()) describe "if not confirmed", -> beforeEach -> @sandbox.stub(window, "confirm").returns(false) it "will not remove the todo from the @page", -> @page.html().should.contain($(@view.el).html()) $(".delete").click() @page.html().should.contain($(@view.el).html()) Monday, February 25, 13
  • 59. WHAT ABOUT AJAX REQUESTS? Monday, February 25, 13
  • 60. app/assets/javascripts/views/todos/todo_list_view.js.coffee class OMG.Views.TodosListView extends OMG.Views.BaseView el: "#todos" initialize: -> @collection.on "reset", @render @collection.on "add", @renderTodo @collection.fetch() render: => $(@el).html("") @collection.forEach (todo) => @renderTodo(todo) renderTodo: (todo) => view = new OMG.Views.TodoView(model: todo, collection: @collection) $(@el).prepend(view.el) Monday, February 25, 13
  • 61. spec/javascripts/views/todos/todo_list_view_spec.coffee #= require spec_helper describe "OMG.Views.TodosListView", -> beforeEach -> @page.html("<ul id='todos'></ul>") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) it "fetches the collection", -> @collection.should.have.length(2) it "renders the todos from the collection", -> el = $(@view.el).html() el.should.match(/Do something!/) el.should.match(/Do something else!/) it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) el = $(@view.el).html() el.should.match(/Do another thing!/) Monday, February 25, 13
  • 63. APPROACH #1 MOCK RESPONSES Monday, February 25, 13
  • 64. 1. DEFINE TEST RESPONSE(S) Monday, February 25, 13
  • 65. spec/javascripts/support/mock_responses.coffee window.MockServer ?= sinon.fakeServer.create() MockServer.respondWith( "GET", "/todos", [ 200, { "Content-Type": "application/json" }, ''' [ {"body":"Do something!","completed":false,"id":1}, {"body":"Do something else!","completed":false,"id":2} ]''' ] ) Monday, February 25, 13
  • 67. spec/javascripts/views/todos/todo_list_view_spec.coffee #= require spec_helper describe "OMG.Views.TodosListView", -> beforeEach -> @page.html("<ul id='todos'></ul>") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) it "fetches the collection", -> @collection.should.have.length(2) it "renders the todos from the collection", -> el = $(@view.el).html() el.should.match(/Do something!/) el.should.match(/Do something else!/) it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) el = $(@view.el).html() el.should.match(/Do another thing!/) Monday, February 25, 13
  • 68. spec/javascripts/views/todos/todo_list_view_spec.coffee #= require spec_helper describe "OMG.Views.TodosListView", -> beforeEach -> @page.html("<ul id='todos'></ul>") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) MockServer.respond() it "fetches the collection", -> @collection.should.have.length(2) it "renders the todos from the collection", -> el = $(@view.el).html() el.should.match(/Do something!/) el.should.match(/Do something else!/) it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) el = $(@view.el).html() el.should.match(/Do another thing!/) Monday, February 25, 13
  • 70. APPROACH #2 STUBBING Monday, February 25, 13
  • 71. spec/javascripts/views/todos/todo_list_view_spec.coffee #= require spec_helper describe "OMG.Views.TodosListView (Alt.)", -> beforeEach -> @page.html("<ul id='todos'></ul>") @todo1 = new OMG.Models.Todo(id: 1, body: "Do something!") @todo2 = new OMG.Models.Todo(id: 2, body: "Do something else!") @collection = new OMG.Collections.Todos() @sandbox.stub @collection, "fetch", => @collection.add(@todo1, silent: true) @collection.add(@todo2, silent: true) @collection.trigger("reset") @view = new OMG.Views.TodosListView(collection: @collection) it "fetches the collection", -> @collection.should.have.length(2) it "renders the todos from the collection", -> el = $(@view.el).html() el.should.match(new RegExp(@todo1.get("body"))) el.should.match(new RegExp(@todo2.get("body"))) Monday, February 25, 13
  • 74. rake konacha:run ......................... Finished in 6.77 seconds 25 examples, 0 failures rake konacha:run SPEC=views/todos/todo_list_view_spec ... Finished in 5.89 seconds 3 examples, 0 failures Monday, February 25, 13
  • 75. THANK YOU @markbates http://www.metacasts.tv CONFOO2013 Monday, February 25, 13