14

In an initializer, I'm injecting an object into my helper, and it works properly in the App.

But when testing the helper with QUnit, I get the following error:

TypeError: undefined is not a function.

The helper doesn't have access to the injected object, although when calling App.__container__.lookup('myObject:main') within the setup function of the module, it does return the object.

How can I make this work? The test class is based on fiddle1, fiddle2.

The following example in CoffeeScript shows my problem:

App = undefined
entered = false
initializedCount = 0

module 'testing',
  setup: ->
    App = startApp()

  teardown: ->
    Ember.run(App, 'destroy')

Ember.Application.initializer({
  name: "person",
  initialize: (container, application) ->
    initializedCount++;
    person = {
      name: "Mary"
    }
    container.register('person:main', person, {instantiate: false});
    container.injection('helper', 'person', 'person:main');
});

createView = (template, context) ->
  context = {}  unless context
  View = Ember.View.extend(
    controller: context
    template: Ember.Handlebars.compile(template)
  )
  View.create()

append = (view) ->
  Ember.run ->
    view.appendTo "#ember-testing"
    return
  return


Ember.Handlebars.helper "upcase", (value) ->
  person = @get('person'); # <-- test fails here
  value += person.name;
  value.toUpperCase()

Ember.testing = true

test('non-redirect route /third', ->
  equal(initializedCount, 2, 'initializer ran');
  App.reset();
  equal(initializedCount, 3, 'initializer ran');
  App.reset();
  equal(initializedCount, 4, 'initializer ran');
);

test "a handlebars helper", ->
  view = createView("{{upcase 'something'}}")
  append view
  equal view.$().text(), "SOMETHING MARY"
  return
jacefarm
  • 6,747
  • 6
  • 36
  • 46
effel
  • 260
  • 2
  • 10

2 Answers2

1

In recent Ember the moduleFor() syntax has been deprecated, but the this.owner.lookup() method that is the migration path for controllers and other instances doesn't seem to return an instance with context for helpers.

I couldn't find any documentation on testing helpers with dependencies, but after consulting Ember's own testing, I found that Application.factoryFor() seems to do the trick:

import { module, test } from 'qunit'
import { setupTest } from 'ember-qunit'

module('Unit | Helper | example-helper', function (hooks) {
  setupTest(hooks)
  let exampleHelper
  hooks.beforeEach(function () {
    exampleHelper = this.owner.factoryFor('helper:example-helper').create()
  })
  test('base', function (assert) {
    assert.equal(exampleHelper.compute([params]), 'expected result')
  })
})

Using factoryFor().create() did construct an instance with the proper context and injected dependencies. I don't know if this is the recommended method or not, but it seems to do what I wanted it to!

cincodenada
  • 2,877
  • 25
  • 35
0

You could consider using a service to handle the data. The service could then be injected into the helper, which could then be unit tested quite easily.

Helpers in Ember 2.x are now "real" objects and can have access to services.

The service might look something like this:

import Ember from 'ember';

export default Ember.Service.extend({
  profile: { 
    name: 'mary',
  },
});

The helper would receive data from the service via an injection:

import Ember from 'ember';

export default Ember.Helper.extend({
  person: Ember.inject.service(), // <-- here

  compute(params) {
    const text = params[0];
    const name = this.get('person.profile.name');
    const phrase = `${text} ${name}`;
    return this.upperCase(phrase);
  },

  upperCase(str) {
    return str.toUpperCase();
  },
});

And finally, the unit test would merely include the Person service as a required need:

import { moduleFor, test } from 'ember-qunit';

moduleFor('helper:upper-case', {
  needs: [ 'service:person' ], // <-- here
});

test('when the upperCase helper is used', function(assert) {
  const helper = this.subject();
  const result = helper.compute([ 'goodbye' ]);
  const expected = 'GOODBYE MARY';

  assert.equal(result, expected, 
               '...the dependency is injected and all are uppercased');
});

Furthermore, the service could also be injected across your application via an initializer, like this:

export function initialize(application) {
  application.inject('controller', 'person', 'service:person');
  application.inject('component', 'person', 'service:person');
  application.inject('route', 'person', 'service:person');
  // etc.
}

export default {
  name: 'person',
  initialize,
};

I've created an Ember Twiddle to demonstrate.

jacefarm
  • 6,747
  • 6
  • 36
  • 46