I am having trouble testing a component with OnPush
change detection strategy.
The test goes like this
it('should show edit button for featured only for owners', () => {
let selector = '.edit-button';
component.isOwner = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css(selector))).toBeFalsy();
component.isOwner = true;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css(selector))).toBeTruthy();
});
If I use Default
strategy it works as expected, but with OnPush
the change to isOwner
is not rerendered by the call to detectChanges
. Am I missing something?
OnPush means that the change detector's mode will be set to CheckOnce during hydration. Default means that the change detector's mode will be set to CheckAlways during hydration.
The OnPush strategy changes Angular's change detection behavior in a similar way as detaching a component does. The change detection doesn't run automatically for every component anymore. Angular instead listens for specific changes and only runs the change detection on a subtree for that component.
But with OnPush strategy, the change detector is only triggered if the data passed on @Input() has a new reference. This is why using immutable objects is preferred, because immutable objects can be modified only by creating a new object reference.
An OnPush change detector gets triggered in a couple of other situations other than changes in component Input() references, it also gets triggered for example: if a component event handler gets triggered. if an observable linked to the template via the async pipe emits a new value.
It doesn't work because the changeDetectorRef in your fixture isn't the same as in your component. Taken from the issue in Angular:
"...changeDetectorRef on a ComponentRef points to the change detector of the root (host) view of a dynamically created component. Then, inside the host view we've got the actual component view, but the component view is OnPush thus we never refresh it!" - source
Option A. One way to solve this is to use the components injector to get the real changeDetectionRef:
describe('MyComponent', () => {
let fixture;
let component;
beforeEach(() => {
TestBed.configureTestingModule({ ... }).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('does something', () => {
// set the property here
component.property = 'something';
// do a change detection on the real changeDetectionRef
fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges();
expect(...).toBe(...);
});
});
You could also just use the initial binding to an @Input (which initially triggers changedetection for an OnPush strategy):
Option B1:
describe('MyComponent', () => {
let fixture;
let component;
beforeEach(() => {
TestBed.configureTestingModule({ ... }).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
});
it('does something', () => {
// set the property here
component.property = 'something';
// do the first (and only) change detection here
fixture.detectChanges();
expect(...).toBe(...);
});
});
or for example:
Option B2:
describe('MyComponent', () => {
let fixture;
let component;
it('does something', () => {
// set the property here
setup({ property: 'something' });
expect(...).toBe(...);
});
function setup(props: { property? } = {}) {
TestBed.configureTestingModule({ ... }).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
Object.getOwnPropertyNames(props).forEach((propertyName) => {
component[propertyName] = props[propertyName];
});
// do the first (and only) change detection here
fixture.detectChanges();
}
});
There are a few solutions, but in your case, I think the easiest way is split your test into two separate tests. If in each of these tests you call fixture.detectChanges()
function only once, everything should works fine.
Example:
it('should hide edit button if not owner', () => {
let selector = '.edit-button';
component.isOwner = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css(selector))).toBeFalsy();
});
it('should show edit button for owner', () => {
let selector = '.edit-button';
component.isOwner = true;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css(selector))).toBeTruthy();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With