TESTING RICH *SCRIPTAPPLICATIONS WITH RAILS         @markbates
Finished in 14.41041 seconds108 examples, 0 failures
Video demo of a Todo list application goes here.
app/models/todo.rbclass Todo < ActiveRecord::Base  validates :body, presence: true  attr_accessible :body, :completedend
spec/models/todo_spec.rbrequire spec_helperdescribe Todo do  it "requires a body" do    todo = Todo.new    todo.should_not...
app/controllers/todos_controller.rbclass TodosController < ApplicationController  respond_to :html, :json  def index    re...
spec/controllers/todos_controller_spec.rbrequire spec_helper                                                              ...
app/views/todos/index.html.erb<form class=form-horizontal id=todo_form></form><ul id=todos class="unstyled"></ul><script> ...
SO WHERE’S THE CODE?
app/assets/javascripts/views/todo_view.js.coffeeclass OMG.Views.TodoView extends OMG.Views.BaseView  tagName: li  template...
HOW DO WE TEST THIS?
CAPYBARA?
XCAPYBARA?
https://github.com/pivotal/jasmine It’s like RSpec for JavaScript*           * or CoffeeScript
JavaScript example:describe(panda,function(){  it(is happy,function(){    expect(panda).toBe(happy);  });});              ...
MATCHERS•   expect(x).toEqual(y)         •   expect(x).toBeFalsy()•   expect(x).toBe(y)            •   expect(x).toContain...
NOT-MATCHERS•   expect(x).not.toEqual(y)         •   expect(x).not.toBeFalsy()•   expect(x).not.toBe(y)            •   exp...
JASMINE WITH RAILS• gem   evergreen, >= 1.0.0, :require => evergreen/rails• gem   capybara-webkit
rake spec:javascripts
LET’S WRITE A TEST!
spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.js# Any othe...
app/assets/javascript/greeter.js.coffeeclass @Greeter  constructor: (@name) ->  greet: ->    "Hi #{@name}"
spec/javascripts/greeter_spec.coffeedescribe "Greeter", ->  beforeEach ->    @greeter = new Greeter("Mark")  it "greets so...
jasmine-jqueryhttps://github.com/velesin/jasmine-jquery
config/environments/development.rbOMG::Application.configure do  # ...  config.assets.paths << Rails.root.join("spec", "jav...
MATCHERS•   toBe(jQuerySelector)        •   toExist()•   toBeChecked()               •   toHaveAttr(attributeName,        ...
spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /a...
app/assets/javascripts/views/todo_view.js.coffeeclass OMG.Views.TodoView extends OMG.Views.BaseView  tagName: li  template...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "OMG.Views.TodoView", ->  beforeEach ->    @collection = new OM...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "model bindings", ->  it "re-renders on change", ->    expect($...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "displaying of todos", ->  it "contains the body of the todo", ...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "checking the completed checkbox", ->  beforeEach ->    expect(...
app/assets/javascripts/todos/todo_view.coffeeclass OMG.Views.TodoView extends OMG.Views.BaseView  # ...  deleteClicked: (e...
MATCHERS•   expect(x).toHaveBeenCalled()•   expect(x).toHaveBeenCalledWith(arguments)•   expect(x).not.toHaveBeenCalled()•...
TRAINING SPIES•   spyOn(x, method).andCallThrough()•   spyOn(x, method).andReturn(arguments)•   spyOn(x, method).andThrow(...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "clicking the delete button", ->  describe "if confirmed", ->  ...
CUSTOM MATCHERS
spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /a...
spec/javascripts/views/todos/todo_view_spec.coffeedescribe "clicking the delete button", ->  describe "if confirmed", ->  ...
WHAT ABOUT AJAX  REQUESTS?
app/assets/javascripts/views/todos/todo_list_view.js.coffeeclass OMG.Views.TodosListView extends OMG.Views.BaseView  el: "...
spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "OMG.Views.TodosListView", ->  beforeEach ->    @_page.htm...
rake spec:javascripts...FF...........  Failed: OMG.Views.TodosListView fetches the collection.    Expected 0 to equal 2.  ...
jasmine-ajaxhttps://github.com/pivotal/jasmine-ajax
spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /a...
1. DEFINE TEST RESPONSE(S)
spec/javascripts/support/mock_responses.coffee@Mocks =  todos:    todo_1:      status: 200      responseText: {"body":"Do ...
2. INSTALL THE MOCK(S)
spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "OMG.Views.TodosListView", ->  beforeEach ->    jasmine.Aj...
3. SET RESPONSES(S)
spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "OMG.Views.TodosListView", ->  beforeEach ->    jasmine.Aj...
rake spec:javascripts................Finished in 2.94 seconds16 examples, 0 failures
THANKS                                         @markbates•   jasmine    https://github.com/pivotal/jasmine•   jasmine-jque...
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
Upcoming SlideShare
Loading in...5
×

Testing Rich Client Side Apps with Jasmine

5,361

Published on

Testing your JavaScript and/or CofffeeScript applications is not nearly as difficult as you think. Learn how to quickly setup Jasmine in your Rails application and start testing your *script applications now!

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
5,361
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
47
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Testing Rich Client Side Apps with Jasmine

    1. 1. TESTING RICH *SCRIPTAPPLICATIONS WITH RAILS @markbates
    2. 2. Finished in 14.41041 seconds108 examples, 0 failures
    3. 3. Video demo of a Todo list application goes here.
    4. 4. app/models/todo.rbclass Todo < ActiveRecord::Base validates :body, presence: true attr_accessible :body, :completedend
    5. 5. spec/models/todo_spec.rbrequire spec_helperdescribe Todo do it "requires a body" do todo = Todo.new todo.should_not be_valid todo.errors[:body].should include("cant be blank") todo.body = "Do something" todo.should be_valid endend
    6. 6. app/controllers/todos_controller.rbclass 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 endend
    7. 7. spec/controllers/todos_controller_spec.rbrequire 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("cant 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("cant 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
    8. 8. 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>
    9. 9. SO WHERE’S THE CODE?
    10. 10. app/assets/javascripts/views/todo_view.js.coffeeclass 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()
    11. 11. HOW DO WE TEST THIS?
    12. 12. CAPYBARA?
    13. 13. XCAPYBARA?
    14. 14. https://github.com/pivotal/jasmine It’s like RSpec for JavaScript* * or CoffeeScript
    15. 15. JavaScript example:describe(panda,function(){ it(is happy,function(){ expect(panda).toBe(happy); });}); CoffeeScript example:describe panda, -> it is happy, -> expect(panda).toBe(happy)
    16. 16. MATCHERS• expect(x).toEqual(y) • expect(x).toBeFalsy()• expect(x).toBe(y) • expect(x).toContain(y)• expect(x).toMatch(pattern) • expect(x).toBeLessThan(y)• expect(x).toBeDefined() • expect(x).toBeGreaterThan(y)• expect(x).toBeUndefined() • expect(function() {fn();}).toThrow(e)• expect(x).toBeNull()• expect(x).toBeTruthy()
    17. 17. NOT-MATCHERS• expect(x).not.toEqual(y) • expect(x).not.toBeFalsy()• expect(x).not.toBe(y) • expect(x).not.toContain(y)• expect(x).not.toMatch(pattern) • expect(x).not.toBeLessThan(y)• expect(x).not.toBeDefined() • expect(x).not.toBeGreaterThan(y)• expect(x).not.toBeUndefined() • expect(function() {fn();}).not.toThrow(e)• expect(x).not.toBeNull()• expect(x).not.toBeTruthy()
    18. 18. JASMINE WITH RAILS• gem evergreen, >= 1.0.0, :require => evergreen/rails• gem capybara-webkit
    19. 19. rake spec:javascripts
    20. 20. LET’S WRITE A TEST!
    21. 21. spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.js# Any other testing specific code here...# Custom matchers, etc....beforeEach -> @_page = $(#test)
    22. 22. app/assets/javascript/greeter.js.coffeeclass @Greeter constructor: (@name) -> greet: -> "Hi #{@name}"
    23. 23. spec/javascripts/greeter_spec.coffeedescribe "Greeter", -> beforeEach -> @greeter = new Greeter("Mark") it "greets someone", -> expect(@greeter.greet()).toEqual("Hi Mark")
    24. 24. jasmine-jqueryhttps://github.com/velesin/jasmine-jquery
    25. 25. config/environments/development.rbOMG::Application.configure do # ... config.assets.paths << Rails.root.join("spec", "javascripts", "support")end
    26. 26. MATCHERS• toBe(jQuerySelector) • toExist()• toBeChecked() • toHaveAttr(attributeName, attributeValue)• toBeEmpty() • toHaveBeenTriggeredOn(selector)• toBeHidden() • toHaveClass(className)• toBeSelected() • toHaveData(key, value)• toBeVisible() • toHaveHtml(string)• toContain(jQuerySelector)
    27. 27. spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /assets/jasmine.jquery.js# Any other testing specific code here...# Custom matchers, etc....beforeEach -> @_page = $(#test)
    28. 28. app/assets/javascripts/views/todo_view.js.coffeeclass 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()
    29. 29. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "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)
    30. 30. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "model bindings", -> it "re-renders on change", -> expect($(.todo-body)).toHaveText("Do something!") @model.set(body: "Do something else!") expect($(.todo-body)).toHaveText("Do something else!")
    31. 31. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "displaying of todos", -> it "contains the body of the todo", -> expect($(.todo-body)).toHaveText("Do something!") it "is not marked as completed", -> expect($([name=completed])).not.toBeChecked() expect($(.todo-body)).not.toHaveClass("completed") describe "completed todos", -> beforeEach -> @model.set(completed: true) it "is marked as completed", -> expect($([name=completed])).toBeChecked() expect($(.todo-body)).toHaveClass("completed")
    32. 32. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "checking the completed checkbox", -> beforeEach -> expect($([name=completed])).not.toBeChecked() $([name=completed]).click() it "marks it as completed", -> expect($([name=completed])).toBeChecked() expect($(.todo-body)).toHaveClass("completed")describe "unchecking the completed checkbox", -> beforeEach -> @model.set(completed: true) expect($([name=completed])).toBeChecked() $([name=completed]).click() it "marks it as not completed", -> expect($([name=completed])).not.toBeChecked() expect($(.todo-body)).not.toHaveClass("completed")
    33. 33. app/assets/javascripts/todos/todo_view.coffeeclass OMG.Views.TodoView extends OMG.Views.BaseView # ... deleteClicked: (e) => e?.preventDefault() if confirm("Are you sure?") @model.destroy() $(@el).remove()
    34. 34. MATCHERS• expect(x).toHaveBeenCalled()• expect(x).toHaveBeenCalledWith(arguments)• expect(x).not.toHaveBeenCalled()• expect(x).not.toHaveBeenCalledWith(arguments)
    35. 35. TRAINING SPIES• spyOn(x, method).andCallThrough()• spyOn(x, method).andReturn(arguments)• spyOn(x, method).andThrow(exception)• spyOn(x, method).andCallFake(function)
    36. 36. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "clicking the delete button", -> describe "if confirmed", -> beforeEach -> spyOn(window, "confirm").andReturn(true) it "will remove the todo from the page", -> expect(@_page.html()).toContain($(@view.el).html()) $(".delete").click() expect(@_page.html()).not.toContain($(@view.el).html()) describe "if not confirmed", -> beforeEach -> spyOn(window, "confirm").andReturn(false) it "will not remove the todo from the page", -> expect(@_page.html()).toContain($(@view.el).html()) $(".delete").click() expect(@_page.html()).toContain($(@view.el).html())
    37. 37. CUSTOM MATCHERS
    38. 38. spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /assets/jasmine.jquery.js# Any other testing specific code here...# Custom matchers, etc....beforeEach -> @_page = $(#test) @addMatchers toContainView: (view) -> actual_html = $(@actual).html() view_html = $(view.el).html() @message = -> "Expected #{actual_html} to contain #{view_html}" return @env.contains_(actual_html, view_html)
    39. 39. spec/javascripts/views/todos/todo_view_spec.coffeedescribe "clicking the delete button", -> describe "if confirmed", -> beforeEach -> spyOn(window, "confirm").andReturn(true) it "will remove the todo from the page", -> expect(@_page).toContainView(@view) $(".delete").click() expect(@_page).not.toContainView(@view) describe "if not confirmed", -> beforeEach -> spyOn(window, "confirm").andReturn(false) it "will not remove the todo from the page", -> expect(@_page).toContainView(@view) $(".delete").click() expect(@_page).toContainView(@view)
    40. 40. WHAT ABOUT AJAX REQUESTS?
    41. 41. app/assets/javascripts/views/todos/todo_list_view.js.coffeeclass 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)
    42. 42. spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "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", -> expect(@collection.length).toEqual(2) it "renders the todos from the collection", -> expect($(@view.el).html()).toMatch("Do something!") expect($(@view.el).html()).toMatch("Do something else!") it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) expect($(@view.el).html()).toMatch("Do another thing!")
    43. 43. rake spec:javascripts...FF........... Failed: OMG.Views.TodosListView fetches the collection. Expected 0 to equal 2. in : Failed: OMG.Views.TodosListView renders the todos from the collection. Expected to match Do something!. in :Finished in 3.12 seconds16 examples, 2 failures
    44. 44. jasmine-ajaxhttps://github.com/pivotal/jasmine-ajax
    45. 45. spec/javascripts/spec_helper.coffee# Require the appropriate asset-pipeline files:require /assets/application.jsrequire /assets/jasmine.jquery.jsrequire /assets/mock-ajax.jsrequire /assets/mock_responses.js# Any other testing specific code here...# Custom matchers, etc....beforeEach -> @_page = $(#test) @addMatchers toContainView: (view) -> actual_html = $(@actual).html() view_html = $(view.el).html() @message = -> "Expected #{actual_html} to contain #{view_html}" return @env.contains_(actual_html, view_html)
    46. 46. 1. DEFINE TEST RESPONSE(S)
    47. 47. spec/javascripts/support/mock_responses.coffee@Mocks = todos: todo_1: status: 200 responseText: {"body":"Do something!","completed":false,"id":1} collection: status: 200 responseText: [ {"body":"Do something!","completed":false,"id":1}, {"body":"Do something else!","completed":false,"id":2} ]
    48. 48. 2. INSTALL THE MOCK(S)
    49. 49. spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "OMG.Views.TodosListView", -> beforeEach -> jasmine.Ajax.useMock() @_page.html("<ul id=todos></ul>") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) it "fetches the collection", -> expect(@collection.length).toEqual(2) it "renders the todos from the collection", -> expect($(@view.el).html()).toMatch("Do something!") expect($(@view.el).html()).toMatch("Do something else!") it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) expect($(@view.el).html()).toMatch("Do another thing!")
    50. 50. 3. SET RESPONSES(S)
    51. 51. spec/javascripts/views/todos/todo_list_view_spec.coffeedescribe "OMG.Views.TodosListView", -> beforeEach -> jasmine.Ajax.useMock() @_page.html("<ul id=todos></ul>") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) request = mostRecentAjaxRequest() request.response(Mocks.todos.collection) it "fetches the collection", -> expect(@collection.length).toEqual(2) it "renders the todos from the collection", -> expect($(@view.el).html()).toMatch("Do something!") expect($(@view.el).html()).toMatch("Do something else!") it "renders new todos added to the collection", -> @collection.add(new OMG.Models.Todo(body: "Do another thing!")) expect($(@view.el).html()).toMatch("Do another thing!")
    52. 52. rake spec:javascripts................Finished in 2.94 seconds16 examples, 0 failures
    53. 53. THANKS @markbates• jasmine https://github.com/pivotal/jasmine• jasmine-jquery https://github.com/velesin/jasmine-jquery/• jasmine-ajax https://github.com/pivotal/jasmine-ajax• evergreen https://github.com/jnicklas/evergreen• capybara-webkit https://github.com/thoughtbot/capybara-webkit• Programming in CoffeeScript http://books.markbates.com
    1. A particular slide catching your eye?

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

    ×