Matteo Depalo's Blog

Will driven life

Ember Integration Testing With Konacha

Ember is truly an awesome framework. The exciting community and the quality of the code has brought joy to front-end development. That said, testing (more importantly integration testing) is a part of the framework that isn’t quite there yet, and for various reasons:

  • Lack of well defined best practices
  • Few complete examples
  • Debugging issues during tests is hard

Problems

The guide on the Ember website is a good start but it’s not enough. It won’t tell you anything about how to handle the run loop during tests, how to work with timers or how to configure the store. If you look around for examples they are either outdated or don’t work with the framework you are using (Mocha, QUnit).

Towards a viable stack

After spending some time trying and failing I believe I’ve reached a stack that makes me happy and that I consider solid enough:

  • Rails (asset pipeline)
  • Konacha (Chai and Mocha)
  • Sinon

Rails might seem an overkill for just the asset pipeline, but currently it’s the most convenient way to build Ember applications. I’ve tried ember-app-kit and although it’s going in the right direction with the ES6 module system-aware resolver, it still has some rough edges like slow compilation times and a vast API surface.

Once you go with Rails you can draw from a nice pool of libraries built around it. Konacha is one of them. If you too think that testing like this is cool, keep reading:

Konacha uses Mocha and Chai in combo. These libraries will make if you feel at home if you’re coming from the RSpec world. It also spins up a web server on the port 3500 that you can visit to run your tests (don’t worry there is still a command for your CI).

The main problem is that Konacha uses Mocha to run tests and Ember supports only QUnit for integration testing out of the box; fortunately teddyzeenny built an adapter for this purpose. Include it in the spec_helper file like this:

1
2
3
4
5
6
7
#= require sinon
#= require application
#= require ember-mocha-adapter

Ember.Test.adapter = Ember.Test.MochaAdapter.create()
App.setupForTesting()
App.injectTestHelpers()

Now you can use Ember test helpers like visit or click without worrying about asynchronous behavior. Just chain them or call then if you want to execute some code after asynchronous actions have been performed. For example:

1
2
3
4
5
6
7
8
describe 'Notices - Integration', ->
  beforeEach ->
    visit('/')

  it 'adds a Notice to the list', ->
    fillIn('input[type="text"]', 'test')
    .click('input[type="submit"]').then ->
      find('.title').text().should.equal('test')

There are some other important things to add to the spec_helper file:

1
2
3
4
5
6
7
8
9
10
11
12
mocha.globals(['Ember', 'DS', 'App', 'MD5'])
mocha.timeout(500)
chai.Assertion.includeStack = true
Konacha.reset = Ember.K

$.fx.off = true

afterEach ->
  App.reset()

App.setup()
App.advanceReadiness()

The first 4 lines will make Konacha play nicely with Ember. They will tell it to ignore leaks on globals and to avoid clearing the body of the application after each test, which is something that Ember doesn’t like.

Removing animations is always a good idea during testing, it will improve speed and cause less accidental problems.

We also tell Mocha to reset the App after each test, which will destroy and reload everything bringing the router to its initial status.

The last lines are important if you have to setup your application before loading it. When you visit localhost:3500 Konacha will load the page and Ember will run App initializers and advance App readiness on document ready. In order to have full control over this process remember to add App.deferReadiness() at the end of the application.coffee file, after creating the App.

If you need to perform some setup before resetting (setup is a custom method I’ve added), override the reset method like this:

1
2
3
4
5
6
7
window.YourApplication = Ember.Application.extend
  setup: ->
    # some setup code

  reset: ->
    @setup()
    @_super()

Under the hood

There are some things that this spec_helper will do under the hood. First of all it will set Ember.testing to true. This will stop the auto-run feature of the Ember RunLoop during tests to give you control over what can run with async side effects and what cannot.

For example if you want to create a fixture you need to wrap it in a Ember.run block or it won’t execute all the async operation that will be scheduled by the application model adapter, like this:

1
2
3
4
Ember.run ->
  notice = App.__container__.lookup('store:main').createRecord('notice', { title: 'test' })
  notice.save().then ->
    # check something

I strongly suggest to read about how the Ember loop works because sooner or later you will need that knowledge in order to debug tests. There is a good SO answer about it.

The Ember.Test.MochaAdapter will also enable the bdd interface for you, so you can use stuff like describe and it during tests.

Stubbing the server

In order to test interactions with a web server some people suggest to switch to FixtureAdapters during tests, but I don’t like this approach because you wouldn’t be testing the actual code of your application and some features, like associations, are implemented properly only on RESTAdapters.

What I’ve found useful instead is mocking the xhr object itself with the sinon.fakeServer. Suppose you want to stub the /api/notices endpoint, which should return a list of notices, you can do it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.server = sinon.fakeServer.create()

server.autoRespond = true

server.respondWith('GET', '/api/notices', [
  200,
  { 'Content-Type': 'application/json' },
  '{ "notices": [
    {
      "id": "a5babb5f-e5b2-4ccf-85fc-4893f8d08d1f",
      "title": "test",
      "created_at": "2014-01-02T14:01:02.810Z"
    }
  ]}'
])

This way you won’t need to change your adapter at runtime and tests will run superfast.

Gotchas

Beware of timers. If your application has long running or self scheduling timers, every function that uses wait under the hood, like visit, will never resolve. It has been discussed that you should be able to explicitly avoid waiting for specific timers during tests, but in the meanwhile you can use the following hack:

Before:

1
2
3
4
5
6
tick: ->
  # do something

  Ember.run.later(this, ->
    @tick()
  , 1000)

After:

1
2
3
4
5
6
7
8
tick: ->
  # do something

  setTimeout(=>
    Ember.run(=>
      @tick()
    )
  , 1000)

This way you won’t use the Ember internal setTimeout (which is not optimal), but you won’t risk of executing async code outside of the run loop while allowing your tests to pass.

Conclusion

Ember is still a relatively young framework which means that you will have to work more to get simple stuff done. However I believe the community is very conscious of this and it’s pushing towards a common and strong approach for getting started quickly and testing.

Comments