SlideShare uma empresa Scribd logo
1 de 71
TESTING RICH *SCRIPT
APPLICATIONS WITH RAILS
         @markbates
Finished in 4.41041 seconds
108 examples, 0 failures
A QUICK POLL
app/models/todo.rb
class Todo < ActiveRecord::Base

  validates :body, presence: true

  attr_accessible :body, :completed

end
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
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
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
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>
SO WHERE’S THE CODE?
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()
HOW DO WE TEST THIS?
CAPYBARA?
X
CAPYBARA?
Mocha + Chai =
Mocha + Chai =
JavaScript example:
describe('panda', function(){
  it('is happy', function(){
    panda.should.be("happy")
  });
});



                CoffeeScript example:
describe 'panda', ->
  it 'is happy', ->
    panda.should.be("happy")
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([])
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)
MOCHA/CHAI WITH RAILS

• gem   'konacha'

• gem   'poltergiest' (brew install phantomjs)
config/initializers/konacha.rb
if defined?(Konacha)
  require 'capybara/poltergeist'
  Konacha.configure do |config|
    config.spec_dir = "spec/javascripts"
    config.driver    = :poltergeist
  end
end
rake konacha:serve
LET’S WRITE A TEST!
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")
app/assets/javascript/greeter.js.coffee
class @Greeter

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

  greet: ->
    "Hi #{@name}"
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")
NOW THE HARD STUFF
chai-jquery
https://github.com/chaijs/chai-jquery
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
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")
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()
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)
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!")
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")
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")
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()
sinon.js
http://sinonjs.org/
SINON.JS
• spies
• stubs
• mocks
• fake   timers
• fake   XHR
• fake   servers
• more
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()
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())
WHAT ABOUT AJAX
  REQUESTS?
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)
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!/)
APPROACH #1
MOCK RESPONSES
1. DEFINE TEST RESPONSE(S)
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}
    ]'''
  ]
)
2. RESPOND
spec/javascripts/views/todos/todo_list_view_spec.coffee
#= require spec_helper

describe "OMG.Views.TodosListView", ->

  beforeEach ->
    page.html(template("todos"))
    @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!/)
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!/)
APPROACH #2
  STUBBING
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")))
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
THANKS
                                            @markbates
•   mocha
    http://visionmedia.github.com/mocha/

•   chai
    http://chaijs.com/

•   konacha
    https://github.com/jfirebaugh/konacha

•   chai-jquery
    https://github.com/chaijs/chai-jquery

•   sinon.js
    http://sinonjs.org/

•   poltergiest
    https://github.com/jonleighton/poltergeist

•   phantom.js
    http://phantomjs.org/

•   Programming in CoffeeScript
    http://books.markbates.com

Mais conteúdo relacionado

Mais procurados

Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmine
Rubyc Slides
 

Mais procurados (20)

G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門
 
Why Every Tester Should Learn Ruby
Why Every Tester Should Learn RubyWhy Every Tester Should Learn Ruby
Why Every Tester Should Learn Ruby
 
Testing Backbone applications with Jasmine
Testing Backbone applications with JasmineTesting Backbone applications with Jasmine
Testing Backbone applications with Jasmine
 
Specs2
Specs2Specs2
Specs2
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
descriptive programming
descriptive programmingdescriptive programming
descriptive programming
 
運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript運用Closure Compiler 打造高品質的JavaScript
運用Closure Compiler 打造高品質的JavaScript
 
Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmine
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Imagine a world without mocks
Imagine a world without mocksImagine a world without mocks
Imagine a world without mocks
 
Scala in practice
Scala in practiceScala in practice
Scala in practice
 
Spine JS
Spine JSSpine JS
Spine JS
 
Vaadin7
Vaadin7Vaadin7
Vaadin7
 
Java and xml
Java and xmlJava and xml
Java and xml
 
SOLID PRINCIPLES
SOLID PRINCIPLESSOLID PRINCIPLES
SOLID PRINCIPLES
 
Javascript basics
Javascript basicsJavascript basics
Javascript basics
 
Vaadin 7
Vaadin 7Vaadin 7
Vaadin 7
 
Stay with React.js in 2020
Stay with React.js in 2020Stay with React.js in 2020
Stay with React.js in 2020
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
 
Doc Parsers Api Cheatsheet 1 0
Doc Parsers Api Cheatsheet 1 0Doc Parsers Api Cheatsheet 1 0
Doc Parsers Api Cheatsheet 1 0
 

Semelhante a Testing JavaScript/CoffeeScript with Mocha and Chai

Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bits
Chris Saylor
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
Sven Haiges
 
Sprout core and performance
Sprout core and performanceSprout core and performance
Sprout core and performance
Yehuda Katz
 
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
Raimonds Simanovskis
 
A tour on ruby and friends
A tour on ruby and friendsA tour on ruby and friends
A tour on ruby and friends
旻琦 潘
 

Semelhante a Testing JavaScript/CoffeeScript with Mocha and Chai (20)

Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bits
 
Refactoring
RefactoringRefactoring
Refactoring
 
Rails2 Pr
Rails2 PrRails2 Pr
Rails2 Pr
 
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
 
Introduction to Kotlin
Introduction to KotlinIntroduction to Kotlin
Introduction to Kotlin
 
Tres Gemas De Ruby
Tres Gemas De RubyTres Gemas De Ruby
Tres Gemas De Ruby
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 
EPiServer report generation
EPiServer report generationEPiServer report generation
EPiServer report generation
 
MongoDB + node.js で作るソーシャルゲーム
MongoDB + node.js で作るソーシャルゲームMongoDB + node.js で作るソーシャルゲーム
MongoDB + node.js で作るソーシャルゲーム
 
Testing with Node.js
Testing with Node.jsTesting with Node.js
Testing with Node.js
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to Nodejs
 
Spock
SpockSpock
Spock
 
Little Big Ruby
Little Big RubyLittle Big Ruby
Little Big Ruby
 
Sprout core and performance
Sprout core and performanceSprout core and performance
Sprout core and performance
 
Rails 2010 Workshop
Rails 2010 WorkshopRails 2010 Workshop
Rails 2010 Workshop
 
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
 
Mongoskin - Guilin
Mongoskin - GuilinMongoskin - Guilin
Mongoskin - Guilin
 
A tour on ruby and friends
A tour on ruby and friendsA tour on ruby and friends
A tour on ruby and friends
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescript
 

Mais de Mark

Mais de 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
 

Último

Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 

Último (20)

Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptx
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 

Testing JavaScript/CoffeeScript with Mocha and Chai

  • 1. TESTING RICH *SCRIPT APPLICATIONS WITH RAILS @markbates
  • 2.
  • 3.
  • 4.
  • 5. Finished in 4.41041 seconds 108 examples, 0 failures
  • 6.
  • 7.
  • 9. app/models/todo.rb class Todo < ActiveRecord::Base validates :body, presence: true attr_accessible :body, :completed end
  • 10. 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
  • 11. 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
  • 12. 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
  • 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>
  • 15. 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()
  • 16. HOW DO WE TEST THIS?
  • 21.
  • 22.
  • 23. JavaScript example: describe('panda', function(){ it('is happy', function(){ panda.should.be("happy") }); }); CoffeeScript example: describe 'panda', -> it 'is happy', -> panda.should.be("happy")
  • 24.
  • 26.
  • 27. 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)
  • 28. MOCHA/CHAI WITH RAILS • gem 'konacha' • gem 'poltergiest' (brew install phantomjs)
  • 29. config/initializers/konacha.rb if defined?(Konacha) require 'capybara/poltergeist' Konacha.configure do |config| config.spec_dir = "spec/javascripts" config.driver = :poltergeist end end
  • 31.
  • 32.
  • 34. 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")
  • 35. app/assets/javascript/greeter.js.coffee class @Greeter constructor: (@name) -> unless @name? throw new Error("You need a name!") greet: -> "Hi #{@name}"
  • 36. 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")
  • 37.
  • 38. NOW THE HARD STUFF
  • 39.
  • 41. 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
  • 42. 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")
  • 43. 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()
  • 44. 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)
  • 45. 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!")
  • 46. 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")
  • 47. 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")
  • 48. 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()
  • 49.
  • 50.
  • 52. SINON.JS • spies • stubs • mocks • fake timers • fake XHR • fake servers • more
  • 53. 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()
  • 54. 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())
  • 55. WHAT ABOUT AJAX REQUESTS?
  • 56. 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)
  • 57. 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!/)
  • 58.
  • 60. 1. DEFINE TEST RESPONSE(S)
  • 61. 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} ]''' ] )
  • 63. spec/javascripts/views/todos/todo_list_view_spec.coffee #= require spec_helper describe "OMG.Views.TodosListView", -> beforeEach -> page.html(template("todos")) @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!/)
  • 64. 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!/)
  • 65.
  • 66. APPROACH #2 STUBBING
  • 67. 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")))
  • 68.
  • 69.
  • 70. 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
  • 71. THANKS @markbates • mocha http://visionmedia.github.com/mocha/ • chai http://chaijs.com/ • konacha https://github.com/jfirebaugh/konacha • chai-jquery https://github.com/chaijs/chai-jquery • sinon.js http://sinonjs.org/ • poltergiest https://github.com/jonleighton/poltergeist • phantom.js http://phantomjs.org/ • Programming in CoffeeScript http://books.markbates.com

Notas do Editor

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n
  50. \n
  51. \n
  52. \n
  53. \n
  54. \n
  55. \n
  56. \n
  57. \n
  58. \n
  59. \n
  60. \n
  61. \n
  62. \n
  63. \n
  64. \n
  65. \n
  66. \n
  67. \n
  68. \n
  69. \n
  70. \n
  71. \n
  72. \n
  73. \n
  74. \n
  75. \n
  76. \n
  77. \n
  78. \n
  79. \n
  80. \n
  81. \n
  82. \n
  83. \n
  84. \n
  85. \n
  86. \n
  87. \n
  88. \n
  89. \n
  90. \n
  91. \n
  92. \n
  93. \n
  94. \n
  95. \n
  96. \n
  97. \n
  98. \n
  99. \n
  100. \n
  101. \n
  102. \n
  103. \n
  104. \n
  105. \n
  106. \n
  107. \n
  108. \n
  109. \n
  110. \n
  111. \n
  112. \n
  113. \n
  114. \n
  115. \n
  116. \n
  117. \n
  118. \n
  119. \n
  120. \n
  121. \n
  122. \n
  123. \n
  124. \n
  125. \n
  126. \n
  127. \n
  128. \n
  129. \n
  130. \n
  131. \n
  132. \n
  133. \n
  134. \n
  135. \n
  136. \n
  137. \n
  138. \n
  139. \n
  140. \n
  141. \n
  142. \n
  143. \n
  144. \n
  145. \n
  146. \n
  147. \n
  148. \n
  149. \n
  150. \n
  151. \n
  152. \n
  153. \n
  154. \n
  155. \n
  156. \n
  157. \n
  158. \n
  159. \n
  160. \n
  161. \n
  162. \n
  163. \n
  164. \n
  165. \n
  166. \n
  167. \n
  168. \n
  169. \n
  170. \n
  171. \n
  172. \n
  173. \n
  174. \n
  175. \n
  176. \n