Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Unit Test with ChangeDetectionStrategy.OnPush doesn't work

I've got this simple component.

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-spinner',
  template: `
    <ng-container *ngIf="loading; else valueTpl">
      <strong>loading....</strong>
    </ng-container>
    <ng-template #valueTpl>
      <span>{{ value }}</span>
    </ng-template>
  `
})
export class SpinnerComponent {
  @Input() loading = false;
  @Input() value!: string;
}

in the running up it works nice but when I go to test the test fails

it('should show spinner when loading true', () => {
    component.loading = true;
    fixture.detectChanges();
    const strong = debugElement.query(By.css('strong'));
    const el: HTMLElement = strong.nativeElement;
    expect(el).not.toBeNull();
  });

Whats the right way to test component with ChangeDetectionStrategy.OnPush?

UPDATE

Waiting in a better solution I worked it out with:

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [SpinnerComponent]
    })
      .overrideComponent(SpinnerComponent, {
        set: { changeDetection: ChangeDetectionStrategy.Default }
      })
      .compileComponents();
  }));
like image 415
user3887366 Avatar asked Jan 23 '20 16:01

user3887366


People also ask

What triggers OnPush change detection?

Input Reference 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.

What is the difference between OnPush and default change detection?

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.

What is Changedetectionstrategy in Angular?

What is Change Detection Strategy in Angular ? Angular Change Detection Strategy are the methods by which the updates to the component is tracked and component is triggered to Re-render.

What is the command to run unit tests in Angular?

Syntax. ng test run the unit test cases on angular app code.


3 Answers

What works is to override component's changeDetection to default AND provide ComponentFixtureAutoDetect as true.

beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [SpinnerComponent],
    providers: [
      {provide: ComponentFixtureAutoDetect, useValue: true}
    ]
  })
  .overrideComponent(SpinnerComponent, {
    set: { changeDetection: ChangeDetectionStrategy.Default }
  })
  .compileComponents();

  fixture = TestBed.createComponent(SpinnerComponent);
  component = fixture.componentInstance;
}));

Now fixture.detectChanges() should work in the tests.

like image 200
Tschonner Avatar answered Nov 15 '22 08:11

Tschonner


The best way is to provide TestingHostComponent that wraps a component you want to test:

@Component({
  template: `<app-spinner [loading]="loading"></app-spinner>`
})
class TestHostComponent {
  @ViewChild(SpinnerComponent, { static: true }) spinnerComponent?: SpinnerComponent;

  loading?: boolean;
}
like image 33
IMalaniak Avatar answered Nov 15 '22 09:11

IMalaniak


I've tried many ways (including @Tschonner's answer) but none of them worked well. Finally, I got this working with the followings, using runOnPushChangeDetection(fixture) instead of fixture.detectChanges():

export async function runOnPushChangeDetection<T>(cf: ComponentFixture<T>) {
  const cd = cf.debugElement.injector.get<ChangeDetectorRef>(
    // tslint:disable-next-line:no-any
    ChangeDetectorRef as any
  );
  cd.markForCheck();
  cd.detectChanges();
  await cf.whenStable();
  return;
}

...

beforeEach(async() => {
    await TestBed.configureTestingModule({
        imports: [
            IonicModule.forRoot()
        ],
        declarations: [ LoaderComponent ],
    })
    .overrideComponent(LoaderComponent, {
        set: { changeDetection: ChangeDetectionStrategy.Default }
    })
    .compileComponents();

    fixture = TestBed.createComponent(LoaderComponent);
    component = fixture.componentInstance;
});

it('should ...', async() => {
    component.prop = true;
    await runOnPushChangeDetection(fixture);

    const debugElement: DebugElement = fixture.debugElement;
    const compiled = debugElement.nativeElement;
    const img = debugElement.query(By.css('img'));
    expect(img).toBeDefined();
    ...
});
like image 21
Akos Hamori Avatar answered Nov 15 '22 09:11

Akos Hamori