簡體   English   中英

Angular 測試沒有失敗但拋出“組件清理期間出錯”類型錯誤:無法讀取未定義的屬性“取消訂閱”

[英]Angular test not failing but throwing 'Error during cleanup of component' TypeError: Cannot read property 'unsubscribe' of undefined

我的測試套件都通過了,但是我拋出了以下錯誤:

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

});

我知道問題出在我們取消訂閱 observable 的ngOnDestroy中。 我嘗試包裝this.subscription.unsubscribe(); 檢查其定義,但我不樂意更改應用程序代碼以使測試通過。

其他一些解決方案提到添加fixture.detectChanges(); 在第一個斷言中should create會觸發ngOnInit但盡管測試現在通過了,但錯誤仍然存在。

有任何想法嗎?

這是因為您還沒有訂閱所有測試用例,並且在這種情況下還沒有形成訂閱。

因此,最好在取消訂閱之前檢查是否已形成訂閱。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM