Testing Rich Client Side Apps with Jasmine

  • 4,941 views
Uploaded 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 …

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!

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
4,941
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
44
Comments
0
Likes
8

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    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

Transcript

  • 1. TESTING RICH *SCRIPTAPPLICATIONS WITH RAILS @markbates
  • 2. Finished in 14.41041 seconds108 examples, 0 failures
  • 3. Video demo of a Todo list application goes here.
  • 4. app/models/todo.rbclass Todo < ActiveRecord::Base validates :body, presence: true attr_accessible :body, :completedend
  • 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. 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. 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. 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. SO WHERE’S THE CODE?
  • 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. HOW DO WE TEST THIS?
  • 12. CAPYBARA?
  • 13. XCAPYBARA?
  • 14. https://github.com/pivotal/jasmine It’s like RSpec for JavaScript* * or CoffeeScript
  • 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. 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. 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. JASMINE WITH RAILS• gem evergreen, >= 1.0.0, :require => evergreen/rails• gem capybara-webkit
  • 19. rake spec:javascripts
  • 20. LET’S WRITE A TEST!
  • 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. app/assets/javascript/greeter.js.coffeeclass @Greeter constructor: (@name) -> greet: -> "Hi #{@name}"
  • 23. spec/javascripts/greeter_spec.coffeedescribe "Greeter", -> beforeEach -> @greeter = new Greeter("Mark") it "greets someone", -> expect(@greeter.greet()).toEqual("Hi Mark")
  • 24. jasmine-jqueryhttps://github.com/velesin/jasmine-jquery
  • 25. config/environments/development.rbOMG::Application.configure do # ... config.assets.paths << Rails.root.join("spec", "javascripts", "support")end
  • 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. 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. 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. 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. 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. 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. 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. 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. MATCHERS• expect(x).toHaveBeenCalled()• expect(x).toHaveBeenCalledWith(arguments)• expect(x).not.toHaveBeenCalled()• expect(x).not.toHaveBeenCalledWith(arguments)
  • 35. TRAINING SPIES• spyOn(x, method).andCallThrough()• spyOn(x, method).andReturn(arguments)• spyOn(x, method).andThrow(exception)• spyOn(x, method).andCallFake(function)
  • 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. CUSTOM MATCHERS
  • 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. 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. WHAT ABOUT AJAX REQUESTS?
  • 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. 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. 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. jasmine-ajaxhttps://github.com/pivotal/jasmine-ajax
  • 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. 1. DEFINE TEST RESPONSE(S)
  • 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. 2. INSTALL THE MOCK(S)
  • 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. 3. SET RESPONSES(S)
  • 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. rake spec:javascripts................Finished in 2.94 seconds16 examples, 0 failures
  • 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