Repository navigation
TestBed: rethink interactions with the @Input / @output of the root component #12313
Description
I'm submitting a ... (check one with "x")
[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
TBH I'm not sure if this is a bug, a feature request, or just a product of my ignorance/incomplete docs.
Current behavior
When I have a component using ChangeDetectionStrategy.OnPush, I can create a test fixture via const fixture = TestBed.createComponent(TestComponent) but can't figure out how to force a change detection after altering one of the input properties. Just calling fixture.detectChanges() like I would for a ChangeDetectionStrategy.Default component doesn't seem to do it. I've also tried in conjunction with fixture.changeDetectorRef.markForCheck() but still no luck. However, if I wrap my OnPush component in another component for testing purposes, it works as expected.
Expected behavior
Should be some way to force a change detection on a OnPush component when changing its inputs directly via the component fixture (rather than via a wrapper component).
Minimal reproduction of the problem with instructions
test.component:
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'my-test',
template: '<p>{{ content }}</p>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestComponent {
@Input() content: string;
}test.component.spec.ts (this test fails w/ OnPush but passes with Default):
import { TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TestComponent } from './test.component';
describe('Test Component', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent]});
});
it('should render the bound content within a p element', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const p = fixture.debugElement.query(By.css('p'));
expect((p.nativeElement as HTMLParagraphElement).innerText).toBeFalsy();
fixture.componentInstance.content = 'Test!';
fixture.detectChanges();
expect((p.nativeElement as HTMLParagraphElement).innerText).toEqual('Test!');
});
});test.component.wrapped.spec.ts (this passes with OnPush or Default):
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TestComponent } from './test.component';
@Component({
selector: 'my-wrapper',
template: '<my-test [content]="content"></my-test>'
})
class WrapperComponent {
content: string;
}
describe('Test Component (Wrapped)', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent, WrapperComponent]});
});
it('should render the bound content within a p element', () => {
const fixture = TestBed.createComponent(WrapperComponent);
fixture.detectChanges();
const p = fixture.debugElement.query(By.css('p'));
expect((p.nativeElement as HTMLParagraphElement).innerText).toBeFalsy();
fixture.componentInstance.content = 'Test!';
fixture.detectChanges();
expect((p.nativeElement as HTMLParagraphElement).innerText).toEqual('Test!');
});
});What is the motivation / use case for changing the behavior?
Would prefer to use roughly the same testing patterns regardless of which change detection strategy I'm using.
Please tell us about your environment:
- Angular version: 2.1.0
- Browser: all
- Language: TS 2.0.3
Activity
mattdistefano commentedon Oct 20, 2016
Thanks! I'll take a look at that and let you know. Also glad to hear that this is supposed to work as-is.
mattdistefano commentedon Oct 20, 2016
Well, setting a default value didn't seem to make a difference, but that suggestion and your example above did help me narrow down the issue a bit. It seems like the first
detectChanges()works, but not subsequent calls.mattdistefano commentedon Oct 20, 2016
Still no luck. The initial value flows through to the DOM but not updates. IIRC the OnPush change detection for a given component more or less runs within the parent component and evaluates its bindings rather than the child component's
@Inputs. So if that's correct, it would sort of make sense that the initialdetectChanges()would work to initialize the component, but, because there's no bindings within the parent (the fixture in this case), subsequent calls don't do anything.Do you guys have any test examples where you're updating an
@Inputafter the component is initialized?alexzuza commentedon Nov 30, 2016
I think that is because you can trigger updating OnPush component only by changing
@Inputproperty from parent component.fixture.changeDetectorRef['internalView']['compView_0'].markAsCheckOnce();Plunker
Plunker Example
All magic happens here
/TestComponent/host.ngfactory.js
ChangeDetectorRefinjected in your test componentthen just use component changeDetector
fixture.componentInstance.cdRef.markForCheck();Plunker Example
jiverson commentedon Dec 1, 2016
Ran across this today spent a few hours scratching my head. At least an example in the Test docs because this can really run you for a loop.
dabalyan commentedon Jan 7, 2017
hey @juliemr is there any update on this ?
Is it something with how test is written, or is this for real ?
@alexzuza 's suggestion works btw.
EddyP23 commentedon Jan 18, 2017
Found same issue. Very frustating! Thanks for the discussion above.
The weirdest thing I found about this is that
fixture.changeDetectorRef.markForCheck()doesn't seem to be doing anything in testsAny ideas when this might get fixed?
sebek64 commentedon Feb 1, 2017
I've found the same. I'm not proficient in angular internals. In my opinion, the problem is that ComponentFixture doesn't provide access to the component itself, just to its wrapper (except fixture.componentInstance, which is a reference to component class). All other properties of fixture are that of wrapper component - changeDetectorRef, injector, componentRef, debugElement, elementRef, nativeElement). In particular, changeDetectorRef, injector, and componentRef are most problematic.
As a temporary solution, I do
compRef = fixture.componentRef.hostView['internalView']['compView_0'];in
beforeEach. Then, instead of doing (quite natural)fixture.changeDetectorRef.markForCheck(), I docompRef.changeDetectorRef.markForCheck();.In my opinion, ComponentFixture should include references to the component itself. If that is not possible for some reason, then there should be these references in addition to those to parent component.
88 remaining items
angular-automatic-lock-bot commentedon Aug 1, 2022
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.