3

I'm trying to write Jasmine/LTS tests for a component that embeds a <lightning:combobox>. The method I'm attempting to test fires a component event within a controller method that is bound to the combo box's onchange attribute, and then resets the combo box's value to a default. It works in actual use.

I've reviewed all of the example tests in the Lightning Testing Service repository, and none of them quite fit this pattern.

The best I've been able to come up with (since doing combobox.set('v.value', SOME_VALUE) from test code does not fire the controller change handler) is to obtain the Aura.Action value for the controller handler via combobox.get('v.onchange'); and directly invoke it with a synthetic event parameter. I can then use Jasmine spies to verify that my component fires its own event correctly.

This code does invoke the right controller method, and the spies work correctly:

combobox.get('v.onchange')();

but this doesn't populate any parameters in the event, like the real combo box does and which my controller relies on. Hence the test case fails.

I've seen this question from a year or so ago that covers the same area but without a clear resolution (see the comments on the accepted answer). I'm also aware that there are more-recommended design options that might change the test structure, but I would like to learn how this works since it is supported with <lightning:combobox> and other components.

So here's the question, ultimately:

  • How do we populate the event parameter when directly invoking an Aura.Action in test context?
  • or, is this the wrong approach to test this type of component-to-component linking?

Herewith some code snippets.

The component:

<aura:component controller="myController">
    <aura:attribute name="statusOptions" type="Object[]" access="PRIVATE" />
    <aura:handler name="init" value="{! this }" action="{! c.doInit }" />
    <aura:registerEvent name="navigateEvent" type="c:navigateEvent" />

    <lightning:combobox aura:id="entitySelector" 
                        name="entitySelector" 
                        label="Select"
                        placeholder="Selection"
                        onchange="{! c.handleComboNavigate }"
                        options="{! v.statusOptions }" />
</aura:component>   

And the controller:

handleComboNavigate : function(component, event, helper) {
    var evt = component.getEvent('navigateEvent');
    var entity = event.getParam('value');

    evt.setParam('entity', entity); // real code has some transformation on `entity` that's not relevant here.                    
    evt.fire();

    event.getSource().set('v.value', null);
}

Current stab at test case:

it('fires event upon navigate', function(done) {
    // Fire the combobox's update event.
    var res = { getState : function() { return 'SUCCESS'; }, 
                getReturnValue: function() { return [ { label: 'Test Train', value: '100'} ]; } };

    // Spy on both enqueueAction, to fake the server controller response...
    // (combo box is populated via Apex controller)
    spyOn($A, 'enqueueAction').and.callFake(function(action) {
        var cb = action.getCallback("SUCCESS")
        cb.fn.apply(cb.s, [res]);
    });

    $T.createComponent('c:entitySelectionComponent', {}, true)
    .then(function(component) {
        // and getEvent, so we can spy on the event firing.
        var eventSpy = jasmine.createSpyObj('eventSpy', ['setParam', 'fire']);
        var getEvent = spyOn(component, 'getEvent').and.returnValue(eventSpy);
        var selectList = component.find('entitySelector');

        selectList.set('v.value', '100');            
        selectList.get('v.onchange')({ 'value' : '100' });

        expect(getEvent).toHaveBeenCalled();
        expect(eventSpy.fire).toHaveBeenCalled();
        expect(eventSpy.setParam).toHaveBeenCalledWith({ selectedEntity: '100' });

        done();
    });
});
1

Here's the "go another way" answer, which allows moving forward with testing but doesn't address the underlying technical questions above regarding how to test a specific type of component-to-component linkage.

Instead of providing a controller action in <lightning:combobox>'s onchange attribute, utilize a value binding to connect the combo box's value to a component attribute:

<aura:component controller="myController">
    <aura:attribute name="statusOptions" type="Object[]" access="PRIVATE" />
    <aura:attribute name="selectedEntity" type="Object" access="PRIVATE" />

    <aura:handler name="init" value="{! this }" action="{! c.doInit }" />
    <aura:handler name="change" value="{! v.selectedEntity }" action="{! c.handleComboNavigate }" />

    <aura:registerEvent name="navigateEvent" type="c:navigateEvent" />

    <lightning:combobox aura:id="entitySelector" 
                        name="entitySelector" 
                        value="{! v.selectedEntity }"
                        options="{! v.statusOptions }" />
</aura:component>   

This makes the component/combobox link accessible in a test, because the component's attribute can be directly set and there's no requirement to populate the controller action's event parameter.

Jasmine spies in this instance both fake the response from the server and show that the correct event-firing calls are made within the component when its value is updated.

it('fires event upon navigate', function(done) {
    var res = { getState : function() { return 'SUCCESS'; }, 
                getReturnValue: function() { return [ { entity: '100'} ]; } };

    // Spy on both enqueueAction, to fake the server controller response...
    spyOn($A, 'enqueueAction').and.callFake(function(action) {
        var cb = action.getCallback("SUCCESS")
        cb.fn.apply(cb.s, [res]);
    });

    $T.createComponent('c:entitySelector', {}, true)
    .then(function(component) {
        // and getEvent, so we can spy on the event firing.
        var eventSpy = jasmine.createSpyObj('eventSpy', ['setParam', 'fire']);
        var getEvent = spyOn(component, 'getEvent').and.returnValue(eventSpy);
        var selectList = component.find('entitySelector');

        selectList.set('v.value', '100');
        expect(getEvent).toHaveBeenCalled();
        expect(eventSpy.fire).toHaveBeenCalled();
        expect(eventSpy.setParam).toHaveBeenCalledWith('entity', { entity: '100' });

        done();
    });
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.