如何在 Angular 单元测试中更新组件变量?
How do I update component variables in Angular unit tests?
我在测试中设置“headerButtons”和“contractLoaded”组件变量时遇到问题,但它似乎没有更改值。如果我在测试运行时控制或在组件中使用调试器,变量将保持最初定义的状态(未定义和错误)。
我尝试了很多不同的组合,但结果总是一样。
headerButtons 是一个@Input
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' && this.contractLoaded) {
this.menubar.set('Contacts', '/iforis/s/contacts/');
this.menubar.set('Notes', '/iforis/s/notes/');
}
if (this.headerButtons === 'full' && !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>
这很奇怪。你能同时展示 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);
});
我在测试中设置“headerButtons”和“contractLoaded”组件变量时遇到问题,但它似乎没有更改值。如果我在测试运行时控制或在组件中使用调试器,变量将保持最初定义的状态(未定义和错误)。
我尝试了很多不同的组合,但结果总是一样。
headerButtons 是一个@Input
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' && this.contractLoaded) {
this.menubar.set('Contacts', '/iforis/s/contacts/');
this.menubar.set('Notes', '/iforis/s/notes/');
}
if (this.headerButtons === 'full' && !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>
这很奇怪。你能同时展示 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);
});