- Getting Started
- DOM Interactions
- jQuery to the Rescue!
- Stubbing Functions
- Mocking AJAX Requests
- Wrapping Up
Mocking AJAX Requests
In pretty much any decent JavaScript application, the last detail of testing is interacting with AJAX requests. Whether you're getting a list of objects or creating a new object through an API call, at some point your application will probably make AJAX requests, which need testing.
When our sample todo application is first loaded, we need to get a list of existing todos from the server and display them to the page. We've already discussed how we display those individual todos, but how do we get that list of todos from the server in our tests? Do we want to connect to a running server and interact with it over actual AJAX requests? Of course not; that would be both problematic and slow. Instead, we'll use Sinon.js again, this time to stub out AJAX requests and responses.
# app/assets/javascripts/views/todos/todo_view.js.coffee class OMG.Views.TodosListView extends Backbone.View 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)
The OMG.Views.TodosListView class works in a simple way. When it's initialized, we give it an empty collection (Backbone.Collection) and tell it to render all of its todos when it receives a reset event. The reset event is triggered when you call the fetch function on the collection. The fetch function makes an AJAX call back to the server to get the list of todos. That's the part we need to make work.
First, let's see how the tests for this should look:
# spec/javascripts/views/todos/todo_list_view.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!/)
These tests are pretty straightforward. We're asserting that we get two todos back from the server and they're printed to the screen. Figure 2 shows what happens when we try to run these tests.
The answer should be obvious immediately: The @collection object isn't receiving a response from the server when the fetch function is called, because we don't have a running server to respond to it.
In the spec/javascripts/support folder, create a new file called mock_responses.coffee that looks like this:
# 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} ]''' ] )
The Sinon library conveniently ships with a sub-library that lets you start a fake server and respond to incoming requests. It's not really starting a server, but rather stubbing out low-level networking protocols in JavaScript.
In the mock_responses.coffee file, we create a new instance of this fake server (if one doesn't already exist). We then tell it this: If it receives a GET request for the URL /todos, return with a response code of 200, some headers, and a string containing a JSON array that represents two todos.
If we ran the specs again right now, they still would fail, because we haven't told Sinon.js to respond to the request. We've indicated what we want the given response to be, but it gives that response only when asked. There are lot of technical reasons why it doesn't respond automatically, but those are outside the scope of this article.
How and when do we tell Sinon.js to respond to the request? We do that after the AJAX call has been made in the test. We can update the beforeEach to look like this:
# spec/javascripts/views/todos/todo_list_view.coffee beforeEach -> page.html(" ") @collection = new OMG.Collections.Todos() @view = new OMG.Views.TodosListView(collection: @collection) MockServer.respond()
Notice that we've added the line MockServer.respond() at the end of the beforeEach block. That will tell Sinon.js to respond to the fetch call that was made in the initializer function in the OMG.Views.TodosListView class.
The tests should now pass. Congratulations!