在清理组件期间使用unsubscribe错误测试Angular组件

Bla*_*axy 36 typescript karma-runner karma-jasmine angular

我正在测试订阅路由器参数的组件.每个测试通过,一切正常.但如果我查看控制台,我会看到一个错误:

清理组件ApplicationViewComponent localConsole时出错.(匿名函数)@ context.js:232

你知道为什么会这样吗?

我尝试删除unsubscribe()from ngOnDestroy()方法,错误消失.

karma/jasmine会unsubscribe()自动支持吗?

这是组件和测试

零件

import { Component, OnInit } from '@angular/core';   
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Rx'

import { AppService } from 'app.service';

@Component({
  selector: 'app-component',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  private routeSubscription: Subscription;

  // Main ID
  public applicationId: string;


  constructor(
    private route: ActivatedRoute,
    private _service: AppService
  ) { }

  ngOnInit() {
    this.routeSubscription = this.route.params.subscribe(params => {
      this.applicationId = params['id'];

      this.getDetails();
      this.getList();
    });
  }

  getDetails() {
    this._service.getDetails(this.applicationId).subscribe(
      result => {     
        console.log(result);
      },
      error => {  
        console.error(error);        
      },
      () => {
        console.info('complete');
      }
    );
  }

  getList(notifyWhenComplete = false) {
    this._service.getList(this.applicationId).subscribe(
      result => {     
        console.log(result);
      },
      error => {  
        console.error(error);        
      },
      () => {
        console.info('complete');
      }
    );
  }

  ngOnDestroy() {
    this.routeSubscription.unsubscribe();
  }

}
Run Code Online (Sandbox Code Playgroud)

组件规范文件

import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
  async,
  fakeAsync,
  ComponentFixture,
  TestBed,
  tick,
  inject
} from '@angular/core/testing';
import {
  RouterTestingModule
} from '@angular/router/testing';
import {
  HttpModule
} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Router, ActivatedRoute } from '@angular/router';

// Components
import { AppComponent } from './app.component';

// Service
import { AppService } from 'app.service';
import { AppServiceStub } from './app.service.stub';

let comp:    AppComponent;
let fixture: ComponentFixture<AppComponent>;
let service: AppService;

let expectedApplicationId = 'abc123';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      imports: [RouterTestingModule, HttpModule],
      providers: [
        FormBuilder,
        {
          provide: ActivatedRoute,
          useValue: {
            params:  Observable.of({id: expectedApplicationId})
          }
        },
        {
          provide: AppService,
          useClass: AppServiceStub
        }    
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    .compileComponents();
  }));

  tests();
});

function tests() {
  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;

    service = TestBed.get(AppService);
  });


  /*
  *   COMPONENT BEFORE INIT
  */
  it(`should be initialized`, () => {
    expect(fixture).toBeDefined();
    expect(comp).toBeDefined();
  });


  /*
  *   COMPONENT INIT
  */

  it(`should retrieve param id from ActivatedRoute`, async(() => {
    fixture.detectChanges();

    expect(comp.applicationId).toEqual(expectedApplicationId);
  }));

  it(`should get the details after ngOnInit`, async(() => {
    spyOn(comp, 'getDetails');
    fixture.detectChanges();

    expect(comp.getDetails).toHaveBeenCalled();
  }));

  it(`should get the list after ngOnInit`, async(() => {
    spyOn(comp, 'getList');
    fixture.detectChanges();

    expect(comp.getList).toHaveBeenCalled();
  }));
}
Run Code Online (Sandbox Code Playgroud)

service.stub

import { Observable } from 'rxjs/Observable';

export class AppServiceStub {
  getList(id: string) {
    return Observable.from([              
      {
        id: "7a0c6610-f59b-4cd7-b649-1ea3cf72347f",
        name: "item 1"
      },
      {
        id: "f0354c29-810e-43d8-8083-0712d1c412a3",
        name: "item 2"
      },
      {
        id: "2494f506-009a-4af8-8ca5-f6e6ba1824cb",
        name: "item 3"      
      }
    ]);
  }
  getDetails(id: string) {
    return Observable.from([      
      {        
        id: id,
        name: "detailed item 1"         
      }
    ]);
  }
}
Run Code Online (Sandbox Code Playgroud)

ran*_*son 69

接受的解决方案不是最佳解决方案,它解决了测试设置不正确的问题.

发生"组件清理期间出错"错误消息,因为ngOnDestroy()调用时this.routeSubscription未定义.发生这种情况是因为ngOnInit()从未调用过,这意味着您从未订阅过该路由.如Angular测试教程中所述,在您fixture.detectChanges()第一次调用之前,组件未完全初始化.

因此,正确的解决方案是在调用后立即添加fixture.detectChanges()beforeEach()块中createComponent.它可以在您创建夹具后随时添加.这样做将确保组件完全初始化,组件清理也将按预期运行.

  • 在我的测试用例`fixture.detectChanges()`添加仍然我收到错误 (11认同)
  • @BlackHoleGalaxy任何理由都没有?如果您在测试时没有完全初始化组件,那么您的测试将无法准确测试组件的行为. (3认同)
  • @BlackHoleGalaxy在这种情况下,您可能不应该使用`TestBed`创建夹具.如果我没记错的话,单元测试完成后,`TestBed`会自动调用组件上的`ngOnDestoy`,所以你需要确保组件在测试过程中完全初始化.如果您的组件具有静态方法,那么您可以单独对其进行单元测试而无需创建夹具,但是应该在组件的完全初始化的实例上测试需要实例的方法. (2认同)

mus*_*ecz 28

你需要重构你的方法ngOnDestroy,如下所示:

ngOnDestroy() {
  if ( this.routeSubscription)
    this.routeSubscription.unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

  • 改变你的代码只是为了工作的测试通常隐藏一个问题...我认为@excaliburHisShealth答案应该是接受的 (8认同)

Ale*_*ink 12

就我而言,每次测试后销毁组件就解决了问题。所以你可以尝试将其添加到你的描述函数中:

afterEach(() => {
  fixture.destroy();
})
Run Code Online (Sandbox Code Playgroud)


Ric*_*ros 7

我处于类似的情况,我想在组件本身的上下文之外测试组件中的函数。

这对我有用:

afterEach(() => {
  spyOn(component, 'ngOnDestroy').and.callFake(() => { });
  fixture.destroy();
});
Run Code Online (Sandbox Code Playgroud)


小智 6

所以我的情况类似,但不完全相同:我只是把它放在这里以防其他人觉得它有用.当我用Jamine/Karma进行单元测试时

 'ERROR: 'Error during cleanup of component','
Run Code Online (Sandbox Code Playgroud)

事实证明这是因为我没有正确处理我的observable,并且他们没有错误函数.所以修复是添加一个错误函数:

this.entityService.subscribe((items) => {
      ///Do work
},
  error => {
    this.errorEventBus.throw(error);
  });
Run Code Online (Sandbox Code Playgroud)


小智 6

你可以加

拆解:{ destroyAfterEach: false },

在 TestBed.configureTestingModule 中,它看起来像这样

beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [<Your component here>],
            imports: [<Your imports here>],
            providers: [<Your providers here>],
            teardown: { destroyAfterEach: false },
        }).compileComponents();
    });
Run Code Online (Sandbox Code Playgroud)