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 anAura.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();
});
});