How Should Your Ember Tests Look?

Tests are obligatory in every production app. They give you confidence in what you’re currently developing, but more importantly, they make sure that maintenance and further features won’t break the business logic. In Ember apps, the distinction between tests is similar to any other Rails application - acceptance, unit and integration tests. But the use cases for them are frequently misunderstood. Let me give a quick summary of what your test suite should look like based on my experience with a huge app, which spent over a year under constant development.

Acceptance tests

The first mistake that a lot of beginners make is to base their confidence on acceptance testing. Why? The reason is simple - most of us find them the easiest at the beginning. You use selectors as reference, perform user actions and the asynchronous helpers do all the work regarding the Ember Run Loop. But I strongly recommend that you don’t stick with acceptance as the most important type of test. First of all, they are extremely slow compared to unit and integration tests. They require the application to reload after each test. Moreover, they only test the page you are visiting, not the components that are used in the page and may be used in other pages (it’s not good to repeat yourself!). Even though acceptance tests look like a quick win, having them as a large part of your test suite is effectively a tech-debt that you’ll have to pay back in the future.
You may find that the tech-debt collector comes knocking at your door in the form of a slow test suite. If so, don’t remove your tests (which would also be a huge mistake - they’re fine but slow). Try to refactor them to use some kind of page objects (see this excellent talk by Lin Reid) and minimize the test cases by merging them in user paths. Instead of the test placing messages in your inbox, leaving, archiving and searching separately, simply perform these tasks in order in a single test case. You’ve just earned a couple of seconds that were taken up by reloading the app each time.

Unit tests

Unit tests are a little bit harder for beginners to understand, but must eventually find a place in your toolbelt. They are extremely fast, and they’re perfect for testing routes and controllers. However, the first hurdle for beginners is how to use ember-data and other services that you inject in your controllers or routes. Actually, I strongly recommend that you mock everything that you can. Test the logic paths that your methods should perform with nearly the same data as real ones. E.g. you don’t need to use ember data in unit tests to create records. You can mock store to return the Ember.Object with same attributes as your model:
test(‘some test that performs findAll on store’, function(assert) {
  let modelRSVP = Ember.RSVP.defer();
  let controller = this.subject({
    store: Ember.Object.create({
      findAll() {
        return modelRSVP.promise;
      }
    }}
  });
  // do something that executes the findAll on store in your tested object
  // (...)
  modelRSVP.resolve(Ember.A([
    Ember.Object.create({ name: ‘Test name’ })
  ]));
  modelRSVP.promise.then(() => {
    // do some asserts for logic after the promise resolves
  });
});Raw

Integration tests

This is still not widely used by developers as it’s quite a recent test option. However, I find it extremely interesting and useful for testing your components. You can easily render components in test scope, test user behavior and do everything that the component does nearly as quickly as using unit tests. Also, if you mock everything as in unit tests, it can be very easy to work with them. Don’t be afraid of the Ember Run Loop as integration tests hide it from you and saves you from any problems with it! Integration tests can be checked out in this excellent blogpost by Alisdair McDiarmid. Unfortunately, they are not properly documented in the Ember Guides - they present acceptance tests as integration tests. It’s misleading, so I suggest you stick to the above blog post.

End to end testing

While working with Ember.JS I found that testing your API and Ember app separately simply doesn’t give enough confidence and may end up with breaking the app when you subsequently make changes in the code. If you mock your API in acceptance tests, you can never be sure if the mocks will accurately mirror the behaviour of the real API. Therefore, a green light on both applications does not mean that the feature you tested on both of them will ultimately work. This has landed me in some extremely stressful spots, so I recommend that you make sure to avoid such situations. End to end testing is something relatively new to me, so I cannot say if our process is flawless - we still need a few weeks of testing. I intend to write a blog post about our setup in a couple of weeks, but for now you can check out this post by Dockyard about their approach.

Summary

In summary, I would like to stress again that testing is a very important part of Ember applications. You should try to divide your test suite to make it both as fast as possible and provide enough confidence to work with code. Take advantage of mocking during unit and integration tests and don’t rely fully on acceptance testing. If your app is a commercial project, don’t forget about end to end testing - make software that is reliable.

Commentaires