-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Closed
Labels
area: testingIssues related to Angular testing features, such as TestBedIssues related to Angular testing features, such as TestBedhotlist: components teamRelated to Angular CDK or Angular MaterialRelated to Angular CDK or Angular Material
Description
So I've got a component that implements ControlValueAccessor and therefore need to test/validate that ngModel works correctly with it. However, I'm finding it non-trivial to test the two-way binding behavior between the model and the view. Here's what my test looks like
describe('usage as a form control', function() {
var fixture: ComponentFixture;
// Component which contains a "model" property bound to md-checkbox with [(ngModel)]
var controller: CheckboxFormcontrolController;
beforeEach(function(done: () => void) {
builder.createAsync(CheckboxFormcontrolController).then(function(_fixture_) {
fixture = _fixture_;
controller = fixture.componentInstance;
fixture.detectChanges();
}).then(done).catch(done);
});
it('supports ngModel', function(done: (err?: Error) => void) {
fakeAsync(function() {
controller.model.isChecked = true;
fixture.detectChanges();
tick();
fixture.detectChanges();
const el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.className).toContain('md-checkbox--checked');
// where "click()" is a helper function that dispatches a click event on `el.nativeElement`
// and then runs `fixture.detectChanges()`
click(el, fixture);
tick();
})();
setTimeout(function() {
var err: Error;
try {
expect(controller.model.isChecked).toBe(false);
} catch(e) {
err = e;
} finally {
done(err);
}
}, 100);
});This setup is the only way I could figure out to verify two-way binding, and there's a lot of complication going on here, specifically:
- The need for fakeAsync() is not immediately obvious. You have to look at how Zone + RxJS is scheduling/running subject subscriptions in order to deduce that this could be helpful.
- The detectChanges()/tick() dance is not straightforward. I believe this is needed as the first call to
detectChanges()will register the model --> view propagation ofisChecked, thentick()is needed to run the zone microtask needed to tell the value accessor to write the new value to the component, and then anotherdetectChanges()is needed to actually process those updates. However, this code looks pretty weird on its own. - (speculative) Internal event handlers seem to run using native timeouts. While the above test will complain if that last
tick()before exiting out of thefakeAsynczone, the assertion currently in the nativesetTimeout()block fails when run within the async zone. Upon debugging this, it appeared to be that even when running insidefakeAsync(), the subscription callback for this particular call was scheduled using the nativesetTimeout()function rather than the mocked one. As a corollary, I tried wrappingcreateAsync()in afakeAsync()zone but when I did so, the promise never settled.
I realize this issue is cumbersome and touches on many different things so please follow up with me on anything that needs more clarification or simply needs to be narrowed down. Thanks!
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area: testingIssues related to Angular testing features, such as TestBedIssues related to Angular testing features, such as TestBedhotlist: components teamRelated to Angular CDK or Angular MaterialRelated to Angular CDK or Angular Material