javascripttestingember.jsintegration-testingember-testing

How to test component methods in Ember.js?


I have component with one method and service, which can run functions for some components.

export default Ember.Component.extend({
  foo: false,

  bar() {
    this.set('foo', true);
  },

  someService: Ember.inject.service('some-service'),

  didInsertElement: function () {
    const someService = this.get('someService');
    someService.registerComponent(this);
  },
});

export default Ember.Service.extend({
  components: [],
  registerComponent: function (component) {
    let components = this.get('components');
    components.push(component);
  },
  runBar(index) {
    let components = this.get('components');
    components[index].bar();
  }
});

1) How can I write tests for bar() method?

2) And also how can I write tests for this service?

3) Or how can I refactor this approach?


Solution

  • I will try to answer your questions in order; before starting with that please check out the following twiddle I have prepared for you. I just copied your component and service definition; but also added willDestroyElement for the component and unregisterComponent for the service to do necessary cleanup to remove the component from the service. Please be aware of the cleanup to be done; you can run into big troubles if you do not consider it at all. Regarding your questions;

    1) You must not call bar method directly for component testing. What you need to do is rendering the component; checking some assertions; and calling runBar method of the service so that bar method of your component will get called within an integration test. This is the scenario in your mind as far as I understand. Please check out my-component-test.js file in the twiddle I provided above. The crucial part in this test might be

     Ember.run(()=>Ember.getOwner(this).lookup('service:some-service').runBar(0));
    

    code snippet. Ember.getOwner(this).lookup() enables you to fetch the service; note that you need to give the full name (service:some-service) to fetch it. It is in a run loop; because it should have an asynchronous affect and Ember warns you about that. Anyway; the test itself is pretty simple I guess; because what the component does is just printing foo property to the screen and upon calling the service's runBar method the property is set to true from the initial value false.

    2) For testing the service; you can write a unit-test and this is what I did in the twiddle. Note that; you do not need actual components for registering to the service; so I used stub components (just dummy instances of Ember.Object in the twiddle). The rest is pretty easy to follow I believe. I just call service's runBar method and check the expected assertions.

    3) Now this is the hard question; because it is not easy to see your intent in making such a design from just the code snippets you have provided. Nevertheless; my comment regarding refactoring is as follows: Please avoid registering components to other constructs (parent components, child components, controllers, routes, services, etc.) as much as possible. IMHO, the component's internal functions should only be callable only by the component itself. The interface of a component should be events to be triggered by the component (I mean the actions to be provided from the outside) and the properties to be passed from the outside. I do not claim your design shall be avoided under all circumstances; but it must be used with care only if no other options is possible. The main reason is that; the traceability of the code with such a design should become a nightmare. Anyone that has access to the service should cause an update to a component (that has registered to the service) it knows nothing about. This is something I really dislike. My suggestion is relying on well defined APIs for a component not passing it around and causing an update on it from an irrelevant place.

    This has been a long post; I hope it helps.