Как обновить переменные компонентов в модульных тестах Angular?

#javascript #angular #unit-testing #jasmine

Вопрос:

У меня возникла проблема, когда я установил переменные компонентов «headerButtons» и «contractLoaded» в своем тесте, но, похоже, значения не изменились. Если я отключу консоль или использую отладчик в компоненте во время выполнения теста, переменные останутся такими, как были определены изначально (неопределенными и ложными).

Я пробовал много разных комбинаций, но всегда один и тот же результат.

Кнопки заголовков-это @Вход

 let component: HeaderBannerComponent;
  let fixture: ComponentFixture<HeaderBannerComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({ declarations: [HeaderBannerComponent] });
    fixture = TestBed.createComponent(HeaderBannerComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
 

 it('should display the correct amount of header buttons', () => {
    const debugEl: DebugElement = fixture.debugElement;

    component.headerButtons = 'basic';
    fixture.detectChanges();

    expect(debugEl.queryAll(By.css('.header-banner__btn-link')).length).toEqual(
      2
    );

    component.headerButtons = 'full';
    component.contractLoaded = true;
    fixture.detectChanges();
    expect(
      debugEl.queryAll(By.css('.header-banner__btn-link')).length
    ).toBeGreaterThan(2);
  });
 

Компонент :

 import { Component, Input, OnInit } from '@angular/core';
import { KeyValue } from '@angular/common';
import { ContractEventService } from 'src/app/core/services/contract-event.service';

@Component({
  selector: 'app-header-banner',
  templateUrl: './header-banner.component.html',
  styleUrls: ['./header-banner.component.css'],
})
export class HeaderBannerComponent implements OnInit {
  menubar: Map<string, string> = new Map<string, string>();
  contractLoaded: boolean = false;

  @Input() headerTitle: any;
  @Input() headerButtons: string;

  constructor(private contractEventService: ContractEventService) {}

  ngOnInit(): void {
    if (this.headerButtons === 'full') {
      this.contractEventService.getContractLoaded().subscribe(
        (rs) => {
          this.contractLoaded = rs;
          this.setButtons();
        },
        (err) => {
          console.warn('failed to get contractLoaded status', err);
        }
      );
    }
    this.setButtons();
  }

  setButtons(): void {
    this.menubar = new Map<string, string>();
    this.menubar.set('Home', '/iforis/main.do');

    if (this.headerButtons === 'full' amp;amp; this.contractLoaded) {
      this.menubar.set('Contacts', '/iforis/s/contacts/');
      this.menubar.set('Notes', '/iforis/s/notes/');
    }
    if (this.headerButtons === 'full' amp;amp; !this.contractLoaded) {
      this.menubar.delete('Contacts');
      this.menubar.delete('Notes');
    }

    this.menubar.set('Exit', '/iforis/exit.do');
  }

  originalOrder = (
    a: KeyValue<string, string>,
    b: KeyValue<string, string>
  ): number => {
    return 0;
  };
}
 

Шаблон:

 <div class="header-banner box-shadow">
  <div class="header-banner__title">
    <span id="headerTitleText" class="header-banner__text">
      {{ headerTitle }}
    </span>
  </div>

  <div class="header-banner__logo">
    <img src="assets/DAFM_Logo_2018.png" />
  </div>

  <div class="header-banner__btn-container">
    <div
      *ngFor="
        let button of menubar | keyvalue: originalOrder;
        let first = first;
        let last = last
      "
      [ngClass]="[
        'header-banner__btn',
        first ? 'header-banner__btn--first' : '',
        last ? 'header-banner__btn--last' : ''
      ]"
    >
      <a href="{{ button.value }}" class="header-banner__btn-link">
        {{ button.key }}
      </a>
    </div>
  </div>
</div>
 

Ответ №1:

Это странно. Можете ли вы также показать HTML и машинописный текст компонента?

Я думаю, что у меня есть идея о проблеме, с которой вы можете столкнуться.

 it('should display the correct amount of header buttons', () => {
    // get rid of this variable
    // const debugEl: DebugElement = fixture.debugElement;

    component.headerButtons = 'basic';
    fixture.detectChanges();

    // Change this line to be fixture.debugElement and not debugEl
    expect(fixture.debugElement.queryAll(By.css('.header-banner__btn-link')).length).toEqual(
      2
    );

    component.headerButtons = 'full';
    component.contractLoaded = true;
    fixture.detectChanges();
    // Change this line to be fixture.debugElement and not debugEl
    expect(
      fixture.debugElement.queryAll(By.css('.header-banner__btn-link')).length
    ).toBeGreaterThan(2);
  });
 

Проблема debugEl в том, что . Он устаревает после изменения переменных компонентов, поэтому debugEl после изменения переменных вам всегда нужна новая. Я думаю, что это, скорее всего, проблема.

====== Редактировать ====== Может быть, нам следует поиздеваться ContractEventService вместо того, чтобы предоставлять настоящий.

Попробуйте это:

 let component: HeaderBannerComponent;
let fixture: ComponentFixture<HeaderBannerComponent>;
let mockContractEventService: jasmine.SpyObj<ContractEventService>;

  beforeEach(() => {
    // the first string argument is just an identifier and is optional
    // the second array of strings are public methods that we need to mock
    mockContractEventService = jasmine.createSpyObj<ContractEventService>('ContractEventService', ['getContractLoaded']);
    TestBed.configureTestingModule({ 
      declarations: [HeaderBannerComponent], 
      // provide the mock when the component asks for the real one
      providers: [{ provide: ContractEventService, useValue: mockContractService }] 
    });
    fixture = TestBed.createComponent(HeaderBannerComponent);
    component = fixture.componentInstance;
    // formatting is off but return a fake value for getContractLoaded
 mockContractEventService.getContractLoaded.and.returnValue(of(true));
    // the first fixture.detectChanges() is when ngOnInit is called
    fixture.detectChanges();
  });

it('should display the correct amount of header buttons', () => {
    // get rid of this variable
    // const debugEl: DebugElement = fixture.debugElement;

    component.headerButtons = 'basic';
    // The issue was that we are not calling setButtons
    // manually call setButtons
    component.setButtons();
    fixture.detectChanges();

    // Change this line to be fixture.debugElement and not debugEl
    expect(fixture.debugElement.queryAll(By.css('.header-banner__btn-link')).length).toEqual(
      2
    );

    component.headerButtons = 'full';
    component.contractLoaded = true;
    // manually call setButtons
    component.setButtons();
    fixture.detectChanges();
    // Change this line to be fixture.debugElement and not debugEl
    expect(
      fixture.debugElement.queryAll(By.css('.header-banner__btn-link')).length
    ).toBeGreaterThan(2);
  });
 

Комментарии:

1. Эй, я обновил свой вопрос, включив в него код компонента и шаблона. Я попробовал ваше предложение выше, но, к сожалению, оно не сработало. У меня есть одна служба, которая вводится в компонент, но я ничего не делаю с этим в тесте, не знаю, будет ли это проблемой?

2. Проверьте мою правку. Главная проблема в том, что мы не вызываем вручную setButtons . Выполняется ngOnInit только один раз, и поэтому все, что происходит после этого, мы должны снова позвонить вручную setButtons . Первый fixture.detectChanges() , кому вы звоните, — это когда ngOnInit звонят, и если вы хотите сделать все свои насмешки, прежде чем звонить первым fixture.detectChanges() , это тоже может сработать, но это может быть больше работы.