使用OnPush组件和fixture.detectChanges()进行角度测试 - 如何使单元测试可靠?

问题描述 投票:0回答:1

在我的应用程序中,我有一个组件,它只是通过某些服务异步获取一些值并将其显示给用户。

组件的代码如下所示:

@Component({
  changeDetection: ChangeDetectionStrategy.Default,
  selector: 'test-component',
  template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
  private readonly someService = inject(SomeService);

  myProperty: string;

  ngOnInit(): void {
    this.someService.getSomeDataAsync().subscribe((value) => {
      this.myProperty = value;
    });
  }
}

并通过一个简单的测试进行了测试,如下所示:

it('should correctly display myProperty in the HTML template of the Component', () => {
  const myMockedValue = 'Some mock value;'
  const { fixture, component, page } = setup();
  const someService = TestBed.inject(SomeService);
  spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);

  fixture.detectChanges();

  expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});

到目前为止一切正常。现在,假设我想将

ChangeDetectionStrategy
更改为
OnPush
。由于
myProperty
的值更新后未触发变化检测,组件将被破坏:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'test-component',
  template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
  private readonly someService = inject(SomeService);

  myProperty: string;

  ngOnInit(): void {
    this.someService.getSomeDataAsync().subscribe((value) => {
      this.myProperty = value;
    });
  }
}

现在,有趣的是测试仍然通过。因为变化检测是由此触发的

fixture.detectChanges()

这是不一致的:现在我的测试告诉我一切正常,但实际上却并非如此。

我知道要修复此错误,我只需在组件中手动调用更改检测:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'test-component',
  template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
  private readonly someService = inject(SomeService);
  private readonly changeDetectorRef = inject(ChangeDetectorRef);

  myProperty: string;

  ngOnInit(): void {
    this.someService.getSomeDataAsync().subscribe((value) => {
      this.myProperty = value;
      this.changeDetectorRef.detectChanges(); // this fixes the problem
    });
  }
}

但是通过此修复,如果我从测试中删除

fixture.detectChanges()
以尝试使其更真实,它将失败:

it('should correctly display myProperty in the HTML template of the Component', () => {
  const myMockedValue = 'Some mock value;'
  const { fixture, component, page } = setup();
  const someService = TestBed.inject(SomeService);
  spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);

  // this will fail
  expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});

我找到了一种让它变得现实的方法,这意味着只有当修复了this.changeDetectorRef.detectChanges()时,测试才会

通过
,而当它不存在时,测试就会失败

这种方式是在测试中手动调用

component.ngOnInit()
,而不是
fixture.detectChanges()
,但是我觉得有点丑:

it('should correctly display myProperty in the HTML template of the Component', () => {
  const myMockedValue = 'Some mock value;'
  const { fixture, component, page } = setup();
  const someService = TestBed.inject(SomeService);
  spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);

  component.ngOnInit();

  expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});

无需从单元测试中手动调用

component.ngOnInit()
来实现相同目标的正确方法是什么?

换句话说,在使用了OnPush变更检测之后,如何确保单元测试是

现实
并且真实地反映了组件在真实应用程序中的行为方式?


注意:这个例子是真实组件的简化版本,可以在这里找到:

https://github.com/azerothcore/Keira3/tree/master/src/app/features/dashboard

angular typescript angular-components angular-test angular-changedetection
1个回答
0
投票

通常在处理

OnPush
时,您应该使用更具反应性的方法。

关键是要使用

AsyncPipe

简单来说,有两种可能的方法

  1. 您正在使用状态管理
    当您订阅 HTTP observable 调用时,只需更新状态即可。在您的模板中,您将通过
    AsyncPipe
    订阅可观察的状态属性。
  2. 您没有使用状态管理
    您可以将方法调用 (
    .subscribe
    ) 分配(不带
    SomeService#getSomeDataAsync
    )到类属性
    myProperty
    。需要注意的是,如果不缓存,每次触发CD时,都会触发多次http调用。只需调用
    pipe
    并确保您使用
    share
    shareReplay
    运算符缓存可观察值。

一般而言,

OnPush
+
AsyncPipe
是有效好友。尝试一下!

© www.soinside.com 2019 - 2024. All rights reserved.