Robert Mosolgo

To my knowledge, batman.js is not maintained. For that reason, I don't suggest that you use it for a new project!

Batman.js Controller Testing with Jasmine

You can use jasmine to test batman.js controllers by looking them up from the controller directory, then executing actions with executeAction.

Setup

To set up,

describe 'PeopleController', ->
  @beforeEach ->
    App.run()
    @peopleController = App.get('controllers.people')

  it 'is present', ->
    expect(@peopleController.constructor).toBe(App.PeopleController)

In our tests, we’ll use Batman.Controller::executeAction to fire controller actions. This way, before-actions and after-actions will be run, too.

Functions Are Called on Records

Use Jasmine spyOn(...).andCallThrough() to make sure functions have been called

  describe 'edit', ->
    # This action is invoked from a view binding, not a route
    # so it takes `person`, not `params`....
    it 'calls transaction on the person', ->
      person = new App.Person(id: 1)
      spyOn(person, 'transaction').andCallThrough()
      @peopleController.executeAction('edit', person)
      expect(person.transaction).toHaveBeenCalled()

Options Passed to Render

Get the most recent render arguments from jasmine’s mostRecentCall. It will be the options passed to @render.

    it 'renders into the dialog', ->
      person = new App.Person(id: 1)
      spyOn(@peopleController, 'render').andCallThrough()
      @peopleController.executeAction('edit', person)
      lastRenderArgs = @peopleController.render.mostRecentCall.args[0]
      lastYield = lastRenderArgs["into"]
      expect(lastYield).toEqual("modal")

Functions Called on Model Classes

Checking class to get is tough becuase there are a lot of them! I just iterate through and make sure nothing jumps out as wrong:

  describe 'index', ->
    it 'gets loaded people, not all people', ->
      spyOn(App.Person, 'get').andCallThrough()
      @peopleController.executeAction('index')
      # there are a lot of calls to App.Person.get, just make sure
      # that "all" wasn't requested!
      loadedCalls = 0
      for call in App.Person.get.calls
        getArg = call.args[0]
        expect(getArg).not.toMatch(/all/)
        if getArg.match(/loaded/)
          loadedCalls += 1
      expect(loadedCalls).toBeGreaterThan(0)

Renders a Specific View

Rendering into the default yield is easy enough – just check layout.subviews for an instance of the desired view.

    it 'renders the PeopleIndexView', ->
      @peopleController.executeAction('index')
      hasPeopleIndexView = App.get('layout.subviews').some (view) -> view instanceof App.PeopleIndexView
      expect(hasPeopleIndexView).toBe(true)