How to fix "ViewDestroyedError: Attempt to use a destroyed view" error in Angular tests?
How to fix "ViewDestroyedError: Attempt to use a destroyed view" error in Angular tests?
首先,有一长串类似的问题(1, 2, 3, 4, 5, , 7, 和更多),但其中 none 实际上有适用于我的情况的答案,还有许多其他问题尚未在全部.
描述和源代码链接
下面的代码很简单Minimal, Reproducible Example of a much bigger project.
当运行从项目目录npm run test
- 预期结果:
- 所有测试通过没有错误
- 实际行为:
- 在 Chromium 中,测试在下面评论为
// FAILING TEST!
未通过并报告 Uncaught Error: ViewDestroyedError: Attempt to use a destroyed view
(link to travis report in the real project)
- 在 Google Chrome 中测试通过,但是如果你打开控制台 (F12) 你会看到同样的错误被记录下来(所以这也是失败,但 Chrome 吞下了它)。
代码
app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
hide: boolean = false;
someSubscription: Subscription;
constructor(private appServiceService: AppServiceService) { }
ngOnInit() {
this.someSubscription = this.appServiceService.shouldHide().subscribe(shouldHide => this.hide = shouldHide);
}
ngOnDestroy() {
this.someSubscription.unsubscribe();
}
}
app.component.html
<div class="row" id="jmb-panel" *ngIf="!hide">
Hello
</div>
app.component.spec
describe('AppComponent', () => {
let component: AppComponent;
let componentDe: DebugElement;
let fixture: ComponentFixture<AppComponent>;
const behaviorSubject = new BehaviorSubject<boolean>(false);
const appServiceStub = {
shouldHide: () => { spy.shouldHideSpyFn(); return behaviorSubject.asObservable() }
};
const spy = { shouldHideSpyFn: () => { } };
let spyShouldHide: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [{ provide: AppServiceService, useValue: appServiceStub }]
}).compileComponents();
}));
beforeEach(() => {
behaviorSubject.next(false);
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
componentDe = fixture.debugElement;
fixture.detectChanges();
spyShouldHide = spyOn(spy, 'shouldHideSpyFn');
});
it('should call AppServiceService#shouldHide on init', () => {
component.ngOnInit();
fixture.detectChanges();
expect(spyShouldHide).toHaveBeenCalledTimes(1);
});
it('should not render div if the AppServiceService#shouldHide observables emit true', () => {
appServiceStub.shouldHide().subscribe((li) => {
if (li) {
fixture.detectChanges();
expect(componentDe.query(By.css('#jmb-panel'))).toBeNull();
}
});
behaviorSubject.next(true);
});
// FAILING TEST!
it('should render div if the AppServiceService#shouldHide observables emit true', () => {
appServiceStub.shouldHide().subscribe((li) => {
if (!li) {
fixture.detectChanges();
expect(componentDe.query(By.css('#jmb-panel'))).not.toBeNull('Jumbotron panel should not be null');
}
});
behaviorSubject.next(false);
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
补充说明:
在发布的规范中指定测试的顺序很重要!如果更改测试顺序,则所有测试都可能通过。这是不正确的:所有测试都应该独立于指定的顺序通过。事实上,在实际项目中,测试是随机失败的:当 jasmine 建立的测试顺序是这样设置的。因此,我无法通过更改测试顺序来“修复”此问题。
问题
为什么会出现这个错误,这是什么意思?更重要的是,
我们如何在 angular 中实施测试时 avoid/fix 这个错误?
您为所有测试创建了一个 BehaviorSubject,您可以在其中订阅它并且永远不会取消订阅,这样它在执行所有测试时都保持活动状态。
Angular 在每个 beforeEach
上运行 TestBed.resetTestingModule() ,这基本上会破坏您的 Angular 应用程序并导致 AppComponent 视图被破坏。但是您的订阅还在。
beforeEach(() => {
behaviorSubject.next(false); (3) // will run all subscriptions from previous tests
...
});
...
// FAILING TEST!
it('should render jumbotron if the user is not logged in', () => {
appServiceStub.shouldHide().subscribe((li) => { // (1)
// will be executed
1) once you've subscribed since it's BehaviorSubject
2) when you call behaviorSubject.next in the current test
3) when you call behaviorSubject.next in beforeEach block
which causes the error since AppComponent has been already destoryed
fixture.detectChanges();
....
});
behaviorSubject.next(false); // (2)
});
要解决该问题,您必须取消订阅每个测试,或者不要对所有测试使用相同的主题:
let behaviorSubject;
...
beforeEach(async(() => {
behaviorSubject = new BehaviorSubject<boolean>(false)
TestBed.configureTestingModule({
...
}).compileComponents();
}));
首先,有一长串类似的问题(1, 2, 3, 4, 5,
描述和源代码链接
下面的代码很简单Minimal, Reproducible Example of a much bigger project.
当运行从项目目录npm run test
- 预期结果:
- 所有测试通过没有错误
- 实际行为:
- 在 Chromium 中,测试在下面评论为
// FAILING TEST!
未通过并报告Uncaught Error: ViewDestroyedError: Attempt to use a destroyed view
(link to travis report in the real project) - 在 Google Chrome 中测试通过,但是如果你打开控制台 (F12) 你会看到同样的错误被记录下来(所以这也是失败,但 Chrome 吞下了它)。
- 在 Chromium 中,测试在下面评论为
代码
app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
hide: boolean = false;
someSubscription: Subscription;
constructor(private appServiceService: AppServiceService) { }
ngOnInit() {
this.someSubscription = this.appServiceService.shouldHide().subscribe(shouldHide => this.hide = shouldHide);
}
ngOnDestroy() {
this.someSubscription.unsubscribe();
}
}
app.component.html
<div class="row" id="jmb-panel" *ngIf="!hide">
Hello
</div>
app.component.spec
describe('AppComponent', () => {
let component: AppComponent;
let componentDe: DebugElement;
let fixture: ComponentFixture<AppComponent>;
const behaviorSubject = new BehaviorSubject<boolean>(false);
const appServiceStub = {
shouldHide: () => { spy.shouldHideSpyFn(); return behaviorSubject.asObservable() }
};
const spy = { shouldHideSpyFn: () => { } };
let spyShouldHide: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [{ provide: AppServiceService, useValue: appServiceStub }]
}).compileComponents();
}));
beforeEach(() => {
behaviorSubject.next(false);
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
componentDe = fixture.debugElement;
fixture.detectChanges();
spyShouldHide = spyOn(spy, 'shouldHideSpyFn');
});
it('should call AppServiceService#shouldHide on init', () => {
component.ngOnInit();
fixture.detectChanges();
expect(spyShouldHide).toHaveBeenCalledTimes(1);
});
it('should not render div if the AppServiceService#shouldHide observables emit true', () => {
appServiceStub.shouldHide().subscribe((li) => {
if (li) {
fixture.detectChanges();
expect(componentDe.query(By.css('#jmb-panel'))).toBeNull();
}
});
behaviorSubject.next(true);
});
// FAILING TEST!
it('should render div if the AppServiceService#shouldHide observables emit true', () => {
appServiceStub.shouldHide().subscribe((li) => {
if (!li) {
fixture.detectChanges();
expect(componentDe.query(By.css('#jmb-panel'))).not.toBeNull('Jumbotron panel should not be null');
}
});
behaviorSubject.next(false);
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
补充说明:
在发布的规范中指定测试的顺序很重要!如果更改测试顺序,则所有测试都可能通过。这是不正确的:所有测试都应该独立于指定的顺序通过。事实上,在实际项目中,测试是随机失败的:当 jasmine 建立的测试顺序是这样设置的。因此,我无法通过更改测试顺序来“修复”此问题。
问题
为什么会出现这个错误,这是什么意思?更重要的是,
我们如何在 angular 中实施测试时 avoid/fix 这个错误?
您为所有测试创建了一个 BehaviorSubject,您可以在其中订阅它并且永远不会取消订阅,这样它在执行所有测试时都保持活动状态。
Angular 在每个 beforeEach
上运行 TestBed.resetTestingModule() ,这基本上会破坏您的 Angular 应用程序并导致 AppComponent 视图被破坏。但是您的订阅还在。
beforeEach(() => {
behaviorSubject.next(false); (3) // will run all subscriptions from previous tests
...
});
...
// FAILING TEST!
it('should render jumbotron if the user is not logged in', () => {
appServiceStub.shouldHide().subscribe((li) => { // (1)
// will be executed
1) once you've subscribed since it's BehaviorSubject
2) when you call behaviorSubject.next in the current test
3) when you call behaviorSubject.next in beforeEach block
which causes the error since AppComponent has been already destoryed
fixture.detectChanges();
....
});
behaviorSubject.next(false); // (2)
});
要解决该问题,您必须取消订阅每个测试,或者不要对所有测试使用相同的主题:
let behaviorSubject;
...
beforeEach(async(() => {
behaviorSubject = new BehaviorSubject<boolean>(false)
TestBed.configureTestingModule({
...
}).compileComponents();
}));