Search code examples
ember.jsember-dataember-app-kit

Ember App Kit and testing model hook


In Ember App Kit, there are a number of testing examples that ship with the initial repo. One of those is a basic Route Unit test. This test is trivial, if the data is hard-coded in the model hook, like this:

test("#model", function(){
  deepEqual(route.model(), ['red', 'yellow', 'blue']);
});

How do you use the isolated container to test the model hook if it returns a promise from ember-data?

Here's the test:

import Activities from 'appkit/routes/activities';

var route;
module("Unit - ActivitiesRoute", {
  setup: function(){
    var container = isolatedContainer([
      'route:activities'
    ]);

    route = container.lookup('route:activities');
  }
});

test("#model", function(){
  deepEqual(route.model(), ['activity', 'activity2', 'activity3']);
});

And the actual Route's model hook:

export default Ember.Route.extend({
  model: function() {
    return this.get('store').find('activity');
  }
});

UPDATE:

After implementing the different approaches below from kingpin2k. Here is a summary of the outcomes.

First approach: works great ... yet no promise.

Second approach: returns the promise object (appears to be resolved), but the array, and correct values are assigned to _detail property.

test("#model", function(){
  deepEqual(route.model()['_detail'], ['activity', 'activity2', 'activity3']); //passes
});

I'd like for store creation to be taken care of within the module setup().

...
module("Unit - ActivitiesRoute", {
  setup: function(){
    var container = isolatedContainer([
      'route:activities'
    ]);

    route = container.lookup('route:activities');

    var store = {
       find: function(type){
         return new Em.RSVP.Promise(function(resolve){
            resolve(['activity', 'activity2', 'activity3']);  // or made up model(s) here
          });
       }
    };
    route.set('store', store);
  }
});

And the test:

test("#model", function(){
  deepEqual(route.model(), ['activity', 'activity2', 'activity3']); // ???
});

Third approach:

...
module('Unit - ActivitiesRoute', {
  setup: function() {
    var container = isolatedContainer([
      'route:activities'
    ]);

    route = container.lookup('route:activities');

    var store = {
      find: function() {

        var promise = new Ember.RSVP.Promise(function(resolve) {
          Em.run.later(function() {
            resolve(Activity.FIXTURES);
          }, 10);
        });

        return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
          promise: promise
        });
      }
    };
    route.set('store', store);
  }
});

And in the test, calling route.model() returns an empty object {} :

test("#model", function(){
  deepEqual(route.model(), Activity.FIXTURES); // returns {}
});

UPDATE #2

It was also necessary to add asyncTest() instead of test() and to also call start() to prevent the test runner from hanging.

asyncTest('#model', function(){
  Em.run(function(){
    route.model().then(function(result){
      ok(result);
      equal(result, Activity.FIXTURES);
      start();
    });
  });
});

Solution

  • Simple approach, it's a unit test, so really you aren't testing the store, so setup a mock store and result.

    route = container.lookup('route:activities');
    var store = {
       find: function(type){
          equal(type, 'activity', 'type is activity');
          return ['activity', 'activity2', 'activity3'];
       }
    }
    
    route.set('store', store);
    

    Even better you can also replicate the promise

    route = container.lookup('route:activities');
    var store = {
       find: function(type){
          equal(type, 'activity', 'type is activity');
          return new Em.RSVP.Promise(function(resolve){
            resolve(['activity', 'activity2', 'activity3']);  // or made up model(s) here
          });
       }
    }
    
    route.set('store', store);
    

    If you want to more closely replicate Ember Data you might use an ArrayProxy implementing the PromiseProxyMixin...

    route = container.lookup('route:activities');
    var store = {
       find: function(type){
          equal(type, 'activity', 'type is activity');
          var promise = new Ember.RSVP.Promise(function(resolve){
            Em.run.later(function(){
              resolve(['activity', 'activity2', 'activity3']);
            }, 10);
          });
    
          return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
            promise: promise
          });
       }
    }
    
    route.set('store', store);
    

    Update

    Using your last approach you should implement it like this

    test("#model", function(){
      route.model().then(function(result){
        deepEqual(result, Activity.FIXTURES); // returns {}
      });
    });
    

    But, there is something tricky here, since it has an async response you'll want to wrap it in an Ember run loop

    test("#model", function(){
      Em.run(function(){
        route.model().then(function(result){
          deepEqual(result, Activity.FIXTURES); // returns {}
        });
      });
    });