Pavol
Pavol

Reputation: 1220

Mocking component function for the purposes of component integration test in EmberJS

Let'a assume a simple EmberJS component:

//my-app/components/my-component.js
export default Ember.Component.extend({

  classNames: ['cursor-pointer'],

  doSth(){
      // whatever, state of the component does not change
  },

  clickListener: Ember.on('click', function(){
      this.doSth();
  })
});

Now I would like to use integration tests to find out whether clicking the component reaches (delegates to) the doSth() method.

moduleForComponent('my-component', 'Integration | Component | stores parser previewer', {
  integration: true
});
test('should call doSth() on click', function (assert) {

   /* Line 3 - the place I tried to set mocked doSth() up */      

   this.render(hbs`{{my-component}}`);
   this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});

The problem is that I cannot substitute method doSth() with my mock. Thus I never run into assert.ok() statement.

I have tried to insert following statements onto the line 3 in the test:

// idea 1
this.set('doSth', function(){
    assert.ok(true);
  });

// idea 2
this.doSth = function(){
   assert.ok(true);
};

None of the 2 approaches (idea 1, idea 2) worked. this.subject() is also unavailable since it's an integration test, not a unit test.

Update: Imagine doSth() be a function like the openUrlInANewTab() shown below that does not influence the component's state, nor the associated controller's nor route's state. Example:

//my-app/components/my-component.js
... 
import openUrlInANewTab from 'my-app/utils/new-tab-opener'; 
... 
export default Ember.Component.extend({
  ...
  doSth(url){ 
     openUrlInANewTab(url); 
  }

while

// my-app/utils/new-tab-opener.js
export default function openUrlInANewTab(){
      anchor = document.createElement("a");
      ...
      anchor.click();
      ...
}

Upvotes: 1

Views: 2454

Answers (2)

feanor07
feanor07

Reputation: 3368

As an alternative to what Daniel had proposed you might consider using a closure action in case of handling click within your component. What I mean is instead of delegating to one of your methods (what you had done and could not test) or to a service (what Daniel suggested) just trigger an action passed to your component. This way also provides you the opportunity to mock in your test. Moreover, this approach results in a more reactive component; since you inject the behavior to your component from the owner at runtime; which enables you to reuse your custom component within different contexts (different parent components/templates) easily. See the twiddle for an example of what I mean.

Upvotes: 1

Daniel
Daniel

Reputation: 18692

You can move doSth to service custom-service that you inject to your my-component. Then in test you can EASILY test this by injecting fake service.

tests/integration/components/my-component-test.js:

test('should call doSth() on click', function(assert) {
  const customStub = Ember.Service.extend({
    doSth() {
      // whatever, state of the component does not change
      console.log('Stubbed doSth fired');
      assert.ok(true, 'Stubbed doSth fired');
    }
  });
  this.register('service:custom-service', customStub);
  this.inject.service('custom-service', { as: 'customService' });
  this.render(hbs`{{my-component}}`);
  this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});

components/my-component.js:

import Ember from 'ember';

export default Ember.Component.extend({
  customService: Ember.inject.service('custom-service'),
  classNames: ['cursor-pointer'],

  clickListener: Ember.on('click', function(){
    this.get('customService').doSth();
    console.log('Click listener');
  })
});

Upvotes: 3

Related Questions