Angular проверка не завершилась, но выдает «Ошибка при очистке компонента». Ошибка типа: невозможно прочитать свойство «отписаться» от неопределенного - PullRequest
0 голосов
/ 25 апреля 2020

Все мои тестовые наборы пройдены, но выдается следующая ошибка:

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

}

Любая тестовая спецификация c:

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 где мы отписываемся от наблюдаемого. Я попытался обернуть this.subscription.unsubscribe(); в проверку его определения, но я не был рад изменить код приложения для прохождения тестов.

В некоторых других решениях упоминалось, что добавление fixture.detectChanges(); в первом утверждении should create вызовет ngOnInit, но хотя тесты теперь проходят, ошибка остается.

Есть идеи?

1 Ответ

1 голос
/ 25 апреля 2020

Это потому, что вы не подписались во всех тестовых случаях, и в этом случае подписка еще не сформирована.

Итак, перед проверкой отписки лучше проверить, сформирована ли подписка или нет.

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