0

我的测试套件都通过了,但是我抛出了以下错误:

'Error during cleanup of component' TypeError: Cannot read property 'unsubscribe' of undefined

这真的比其他任何事情都烦人,但我不知道如何删除它。

我的组件编写如下:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { UserService } from 'src/app/services/user.service';
import { User } from 'src/models/user';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit, OnDestroy {

  users: Array<User> = [];
  private subscription: Subscription;

  constructor(private service: UserService) { }

  ngOnInit(): void {
    this.subscription = this.service.getUserList().subscribe(data => this.users = data.list.entries)
  }

  ngOnDestroy(): void {
      this.subscription.unsubscribe();
  }

  remove(id: string): Array<User>{
    return this.users = [...this.users].filter(user => user.entry.id !== id);
  }

}

任何测试规范:

import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';
import { UserService } from 'src/app/services/user.service';
import { HttpClientModule } from '@angular/common/http';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { DebugElement } from '@angular/core';
import { By } from "@angular/platform-browser";
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

describe('UserListComponent', () => {
  let component: UserListComponent;
  let fixture: ComponentFixture<UserListComponent>;
  let userService: UserService;
  let el: DebugElement;
  let users: any = [{
    "entry": {
      "firstName": "Ryan",
      "id": "ryan",
      "enabled": false,
    }
  },
    {
      "entry": {
        "firstName": "Administrator",
        "id": "admin",
        "enabled": true,
      }
    }
  ];

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [UserListComponent],
      providers: [UserService],
      imports: [HttpClientModule, MatChipsModule, MatIconModule]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
    userService = TestBed.get(UserService);
  });

  afterEach(() => {
    fixture.destroy();
  })

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });

  it('load users OnInit', fakeAsync(() => {
      spyOn(userService, 'getUserList').and.returnValue(of({
          list: {
            entries: users
          }
      }).pipe(delay(1)));

      fixture.detectChanges();

      expect(component.users).toEqual([]);
      expect(userService.getUserList).toHaveBeenCalled();

      tick(1);

      expect(component.users).toEqual(users);
  }));

  it('render the user list', fakeAsync(() => {
      spyOn(userService, 'getUserList').and.returnValue(of({
          list: {
            entries: users
          }
      }).pipe(delay(1)));

      fixture.detectChanges();
      tick(1);
      fixture.detectChanges();
      el = fixture.debugElement.query(By.css('mat-chip-list'));
      expect(el).toBeDefined();
      expect(el.queryAll(By.css('mat-chip')).length).toEqual(2);
  }));

  it('should remove a user from the list', fakeAsync(() => {
    spyOn(userService, 'getUserList').and.returnValue(of({
        list: {
          entries: users
        }
    }).pipe(delay(1)));
    spyOn(component, 'remove').and.callThrough();

    fixture.detectChanges();
    tick(1);
    fixture.detectChanges();

    let removeIcons = fixture.debugElement.queryAll(By.css('mat-icon'));

    expect(removeIcons.length).toEqual(1);

    removeIcons[0].triggerEventHandler('click', {stopPropagation: function(){return false;}});

    fixture.detectChanges();

    expect(component.remove).toHaveBeenCalled();
    expect(component.remove).toHaveBeenCalledWith('admin');
    expect(component.users.length).toEqual(1);

    let chips = fixture.debugElement.queryAll(By.css('mat-chip'));
    expect(chips.length).toEqual(1);
  }));

  it('should differentiate an "enabled" user', () => {
    component.users = users;
    fixture.detectChanges();
    let chips = fixture.nativeElement.querySelectorAll('mat-chip');
    component.users.forEach((user, index) => {
        expect(chips[index].classList.contains('mat-chip-with-trailing-icon')).toBe(user.entry.enabled ? true : false);
        expect(window.getComputedStyle(fixture.nativeElement.querySelectorAll('mat-chip')[index]).backgroundColor).toBe(user.entry.enabled ? 'rgb(173, 255, 47)' : 'rgb(224, 224, 224)');
    });
  });

});

我知道问题出ngOnDestroy在我们取消订阅 observable 的地方。我尝试this.subscription.unsubscribe();对其定义进行检查,但我不乐意更改应用程序代码以使测试通过。

其他一些解决方案提到添加fixture.detectChanges();第一个断言should create会触发ngOnInit,但是尽管测试现在通过了,但错误仍然存​​在。

有任何想法吗?

4

1 回答 1

2

这是因为您还没有订阅所有测试用例,并且在这种情况下还没有形成订阅。

因此,最好在取消订阅之前检查是否已形成订阅。

 ngOnDestroy(): void {
      if(subscription) {
      this.subscription.unsubscribe();
    }
  }

更新- 仅在测试用例结束时需要更改

当您在 ngOnInit 中订阅时,您需要确保在创建组件之前为其提供模拟数据。希望它能解决您的问题。

beforeEach(() => {
          spyOn(userService, 'getUserList').and.returnValue(of({
          list: {
            entries: users
          }
      }).pipe(delay(1)));
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
    userService = TestBed.get(UserService);
  });
于 2020-04-25T20:05:09.870 回答