问题
I've been tasked with writing tests for a chat app developed with Angular. Below is the snippet of Angular template code I'm currently writing tests for:
<div class="title-menu-container" fxLayoutAlign="center center">
<button id="save-title-button" mat-icon-button *ngIf="titleInputEdit; else settings">
<mat-icon class="secondary-text" (click)="saveTitle(titleInput.value)">check</mat-icon>
</button>
<ng-template #settings>
<button mat-icon-button [matMenuTriggerFor]="menu" [disabled]="!(isGroupConversation$ | async)">
<mat-icon class="secondary-text">settings</mat-icon>
</button>
</ng-template>
</div>
Essentially, if the component boolean variable 'titleInputEdit' is true, the save-title-button is displayed, otherwise the settings button is displayed. Here is the test case that is causing problems:
it('save title button should be present', () => {
component.titleInputEdit = true;
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('#save-title-button')).not.toBe(null);
});
I simply "mock" the component variable, call .detectChanges(), and then test for the presence of the button. However, the test fails with 'Expected null not to be null.'
Through various console.log calls, I have confirmed that the component.titleInputEdit is correctly set to true but the fixture.nativeElement DOES NOT contain the correct button.
Some things I have noticed:
If I move the 'component.titleInputEdit = true' line into my beforeEach and remove it, and the detectChanges() call, from my test, the test passes.
beforeEach(() => { fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; component.titleInputEdit = true fixture.detectChanges(); debugElement = fixture.debugElement; }); it('save title button should be present', () => { expect(fixture.nativeElement.querySelector('#save-title-button')).not.toBe(null); });
If I remove the .detectChanges() call from beforeEach(), and leave it in the test case, the test passes.
I'm relatively new to Angular, but I've found instances of people with a similar issue. After trying some of the things recommended in those posts I'm still left scratching my head. What's even stranger is that I have written tests for other Angular components that do almost the exact same thing with no issue.
The example provided in the Angular docs show something very similar as well:
it('should display a different test title', () => {
component.title = 'Test Title';
fixture.detectChanges();
expect(h1.textContent).toContain('Test Title');
});
回答1:
It turns out this is due to using ChangeDetectionStrategy.OnPush in the component. Using OnPush only allows you to call .detectChanges() one time, so subsequent calls will fail to do anything. I'm not familiar enough with Angular to fully understand why.
I was able to produce the required behaviour by overriding the ChangeDetectionStrategy in my TestBed configuration.
TestBed.configureTestingModule({
imports: [],
declarations: [TestComponent],
providers: []
})
.overrideComponent(TestComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents();
回答2:
The answer provided here https://stackoverflow.com/a/50142134/3765819 fixes the problem. However, there is also another way which I think it can prevent further problems on the UI. The problem I had was similar to the one described on the question, meaning that when testing for a specific string on HTML I could not find it. Even though when running the code it worded fine, the UI was not updated accordingly when testing it.
What I had to do was:
To inject ChangeDetectorRef
into the .ts
file:
constructor(private changeDetector: ChangeDetectorRef) {}
and call it when needed:
this.changeDetector.markForCheck();
回答3:
I know this question is old, but I recently had this same issue where a spinner would constantly spin on the Karma page because change detection only occurred once. The fix for me is whether to call fixture.detectChanges(true) or fixture.autoDetectChanges(true).
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
component.titleInputEdit = true
// 'detectChanges' will only test for onPush events:
// fixture.detectChanges();
// 'autoDetectChanges' will continually check for changes until the test is complete.
// This is slower, but necessary for certain UI changes
fixture.autoDetectChanges(true);
debugElement = fixture.debugElement;
});
来源:https://stackoverflow.com/questions/50137734/detectchanges-not-working-within-angular-test