Search code examples
javascriptember.jsqunitember-cli-mirage

'this' undefined only from component integration test


This is an Ember component that will need this at some point:

export default Component.extend({
  filteredSubs: computed.filter('model.subs', function() {
    // this will always return true in development http://localhost:4200/dummy
    // but will always return false in test because 'this' becomes undefined
    return this;
  })
});

Dummy has a one-to-many relationship to Sub:

export default Model.extend({
  subs: hasMany('sub')
});

export default Model.extend({
  dummy: belongsTo('dummy')
});

This test fails but shouldn't:

test('it renders', function(assert) {
  let dummy = server.create('dummy');
  server.create('sub', { dummy });

  this.set('dummy', dummy);
  this.render(hbs`{{show-dummy model=dummy}}`);

  assert.equal(this.$().text().trim(), 'Hi! There are 1 sub-dummies');
});

not ok 13 Chrome 63.0 - Integration | Component | show dummy: it renders

actual: Hi! There are 0 sub-dummies

expected: Hi! There are 1 sub-dummies


Solution

  • Your problem comes from an unfortune sequense of falsy assumptions.

    The first assumption of you is that this inside a Ember.computed.filter should be the corresponding object. I'm not 100% sure this is documented behaviour, and personally wouldnt rely on it. If you need full access to this I would go with a simple Ember.computed.

    However your primary mistake is in your test. And this also explains why you only have this problem in testing. Your directly using a mirage model as model for your component:

    let dummy = server.create('dummy');
    server.create('sub', {
      dummy
    });
    
    this.set('dummy', dummy);
    
    this.render(hbs`{{show-dummy model=dummy}}`);
    

    Here you assume that the result of server.create, a mirage model, is in some ways similar to a ember-data model. It is not! In fact, a mirage model is not even an ember object! So you can't use .get or .set on it, or anything you defined on your model, and definitly should not use it ever as an model für component testing. Instead you should use mirage as data-source for your ember-data models.

    The question why this is undefined if your model is a mirage model leads to this line in ember-cli-mirage:

    filter(f) {
      let filteredModels = this.models.filter(f);
    
      return new Collection(this.modelName, filteredModels);
    }
    

    where the this-context gets lost. Basically mirage is overriding the .filter function on their custom array-like structure, and doesnt ensure to keep the this-context.