如何修复Angular测试中的“ ViewDestroyedError:尝试使用被破坏的视图”错误?

时间:2019-06-12 12:35:44

标签: angular unit-testing jasmine karma-jasmine karma-runner

首先,一长串类似的问题(enter image description here1234567等),但实际上没有一个答案适用于我的情况,而其他许多答案都没有得到答案。


描述和源代码链接

8following code的简单角度Minimal, Reproducible Example的一种尝试。

从项目目录运行npm run test

  • 预期结果:
    1. 所有测试通过 PASS 都没有错误
  • 实际行为:
    1. 在Chromium中,以下评论为 // FAILING TEST! 的测试未通过并报告Uncaught Error: ViewDestroyedError: Attempt to use a destroyed view much bigger project
    2. 在Google Chrome浏览器中,测试通过了,但是,如果您打开控制台( F12 ),则会看到记录了相同的错误(因此也失败了,但是Chrome吞噬了它)。

link to travis report in the real project


代码

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();
  });

});

其他说明:

在发布的规范中指定测试的顺序很重要!如果更改测试顺序,则所有测试都可以通过。这是不正确的:所有测试均应独立于指定的顺序通过。实际上,在实际项目中,测试随机失败:当茉莉花建立的测试顺序是这样设置的。因此,通过更改测试顺序来“修复”该问题对我来说是行不通的。

问题

  • 为什么会发生此错误,这意味着什么?更重要的是,

  • Image: Error in console: Uncaught Error: ViewDestroyedError: Attempt to use a destroyed view 中实施测试时,如何避免/修复此错误?

1 个答案:

答案 0 :(得分:2)

您为所有测试创建了一个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();
}));