javascripttestingember.jsember-testing

Ember component test -- how unit test component's action when it doesn't invoke an external action?


I'm somewhat new to Ember and trying to test a pager component. Simplified component looks like this:

export default Ember.Component.extend({
    index: 0,
    actions: {
        next() {
            this.incrementProperty('index');
        }
    }
});

I'm trying to test that the next() action does indeed increment the index property as expected. I wrote a unit test like this:

test('next should increment the index by 1', function (assert) {
  const component = this.subject();

  assert.equal(component.get('index'), 0, 'Index should be 0');

  component.get('actions').next();
  assert.equal(component.get('index'), 1, 'index should be 1');
});

But it fails with the error "this.incrementProperty is not a function". Debugging, the "this" in the test, when inside the next() function, isn't the context of the component -- it is an object with next() as its only property.

I'm wondering if this is because of how I'm invoking the nextPlace action in the test. I know I could write an integration test that clicks the button that fires this action and compares some UI to make sure it changed, but that seems very convoluted when all I'm trying to test is that the function itself performs as expected. If it were an action passed to this component (closure action) I know I could set up a dummy function in the integration test, pass that to the component, and see how it responds. But this action doesn't call an action passed down to it.

I realize the function I'm testing is really basic, but partly this is to understand how to test actions in components that don't call an external (to the component) action.


Solution

  • If you didn't express your motivation about not writing an integration test, I would only suggest you to write an integration test. IMO, your reason is valid. I've 3 suggestions for booting the unit test for your case.

    First of all: The reason of not working is "component.get('actions').next get a reference to the next function without any context. So this is not a valid in that call. To make it valid, just bind component to it as shown below:

    component.get('actions').next.bind(component)();
    

    But I wouldn't prefer this because it is extracting the next from its context and bind it again. We are doing this bind thing, because we know that the next function has a reference to this in its code.

    So my second suggestion is "triggering the event some how". For triggering it, have a look at the following code:

    component.send('next');
    

    IMO, this is better. We don't need to know "what next is doing". We are just triggering an event.

    But this is dealing with ember component's lifecycle. We accept this situation: There is a specific hash called actions and we can trigger it via send. (This is completely okay.) Instead of dealing with actions, we can seperate doing something from action handling. So you can define another function to do what you need, and simply call it from your action handler. (Ok, in this case we again need to know what function is called by action handler. But this seems more clear for me.) As shown below:

    next(){
      this.incrementProperty('index');
    },
    actions:{
      next(){
        this.next();
      }
    }
    

    You can see all three alternatives at this twiddle.