Angular 检查嵌套 *ngIf 元素是否存在时测试失败
Angular test fails when checking if nested *ngIf element exists
我有以下 HTML 的 chuck,它检查对象 configService.config$.footerLinks 是否存在,如果存在,它会创建一个 UL 元素。在其中进一步检查对象内的字符串,这些字符串对应于它各自的 LI 元素。
<ul id="footer-links" *ngIf="(configService.config$ | async)?.footerLinks">
<li id="footer-link-manual" *ngIf="(configService.config$ | async)?.footerLinks.manual">
<a href="{{(configService.config$ | async)?.footerLinks.manual}}" target="_blank">Manual</a>
</li>
<li id="footer-link-terms-and-conditions" *ngIf="(configService.config$ | async)?.footerLinks.termsAndConditions">
<a href="{{(configService.config$ | async)?.footerLinks.termsAndConditions}}" target="_blank">Terms</a>
</li>
<li id="footer-link-privacy-policy" *ngIf="(configService.config$ | async)?.footerLinks.privacyPolicy">
<a href="{{(configService.config$ | async)?.footerLinks.privacyPolicy}}" target="_blank">Pricacy</a>
</li>
</ul>
我用两个测试写了下面的测试文件
// imports, providers & schemas
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
import { ComponentFixture, TestBed} from '@angular/core/testing';
import { Config } from '@app/shared/models/config';
import { Observable, Subject } from 'rxjs';
import { By } from '@angular/platform-browser';
// services
import { ConfigService } from '@app/shared/services/config.service';
// component page
import { FooterComponent } from './footer.component';
class FakeConfigService {
configSubject = new Subject<Config>();
config$: Observable<Config>;
constructor() {
this.config$ = this.configSubject.asObservable();
}
}
fdescribe('footer-component', () => {
let fixture: ComponentFixture<FooterComponent>;
let fakeConfigService: FakeConfigService;
beforeEach(() => {
fakeConfigService = new FakeConfigService();
TestBed.configureTestingModule({
imports: [],
providers: [
{ provide: ConfigService, useValue: fakeConfigService },
],
declarations: [ FooterComponent ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
}).overrideComponent(FooterComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
fixture = TestBed.createComponent(FooterComponent);
fixture.detectChanges();
});
afterEach(() => {
fakeConfigService.configSubject.unsubscribe();
});
it('Should show the footer links if a footer link is present', () => {
const configSubject: Config = {
allowedGoogleDomains: ['something.com'],
footerLinks: {
manual: 'something.com/manual',
termsAndConditions: 'something.com/termsAndConditions',
privacyPolicy: 'something.com/privacyPolicy'
}
};
fakeConfigService.configSubject.next(configSubject);
fixture.detectChanges();
const footerLinks = fixture.debugElement.query(By.css('#footer-links'));
expect(footerLinks).toBeTruthy();
});
it('Should show the footer manual link if a footer manual link is present', () => {
const configSubject: Config = {
allowedGoogleDomains: ['something.com'],
footerLinks: {
manual: 'something.com/manual',
}
};
fakeConfigService.configSubject.next(configSubject);
fixture.detectChanges();
const footerLinkManual = fixture.debugElement.query(By.css('#footer-link-manual'));
expect(footerLinkManual).toBeTruthy();
});
});
如果填充 configService.config$.footerlinks 后创建了 UL,那么第一个测试就可以通过了。
第二个测试包含手册 link 的 LI 是否失败。当我 console.log fixture.debugElement.query(By.css('#footer-links')) 的 nativeElement 时,它会出现这个
LOG LOG: <ul _ngcontent-a-c1="" id="footer-links"><!--bindings={
"ng-reflect-ng-if": null
}--><!--bindings={
"ng-reflect-ng-if": null
}--><!--bindings={
"ng-reflect-ng-if": null
}--></ul>
因此,无论出于何种原因,测试都会填充 UL 但不会填充连接到 configService.config$.footerlinks.manual 的 LI,即使它们由同一对象填充也是如此。为什么会发生这种情况的任何线索?
应该提到我也尝试使用 fakeAsync/tick() 和 async/fixture.whenStable 并且我得到了相同的结果。任何帮助将不胜感激。
每次模板中有 | async
时,它都会创建一个新的可观察对象订阅。因此,如果您的真实可观察对象是一个 HTTP 可观察对象,它将发送 7 个 HTTP 请求来获取配置。
在你的测试中,当你从你的主题发出并检测到变化时,第一个 ngIf 条件变为真,然后评估里面的条件。但由于主题之前已经发出,他们评估为 false。
您真的需要避免在您的模板中有很多订阅。代码至少应该改成
<ng-container *ngIf="configService.config$ | async as config>
<ul id="footer-links" *ngIf="config.footerLinks">
<li id="footer-link-manual" *ngIf="config.footerLinks.manual">
<a [href]="config.footerLinks.manual" target="_blank">Manual</a>
</li>
<li id="footer-link-terms-and-conditions" *ngIf="config.footerLinks.termsAndConditions">
<a [href]="config.footerLinks.termsAndConditions" target="_blank">Terms</a>
</li>
<li id="footer-link-privacy-policy" *ngIf="config.footerLinks.privacyPolicy">
<a [href]="config.footerLinks.privacyPolicy" target="_blank">Pricacy</a>
</li>
</ul>
</ng-container>
您还应该了解 smart/dumb 模式以避免此类问题并使您的代码更易于测试。
而且模板真的不应该直接从服务访问任何内容。将其封装在您的组件中。
我有以下 HTML 的 chuck,它检查对象 configService.config$.footerLinks 是否存在,如果存在,它会创建一个 UL 元素。在其中进一步检查对象内的字符串,这些字符串对应于它各自的 LI 元素。
<ul id="footer-links" *ngIf="(configService.config$ | async)?.footerLinks">
<li id="footer-link-manual" *ngIf="(configService.config$ | async)?.footerLinks.manual">
<a href="{{(configService.config$ | async)?.footerLinks.manual}}" target="_blank">Manual</a>
</li>
<li id="footer-link-terms-and-conditions" *ngIf="(configService.config$ | async)?.footerLinks.termsAndConditions">
<a href="{{(configService.config$ | async)?.footerLinks.termsAndConditions}}" target="_blank">Terms</a>
</li>
<li id="footer-link-privacy-policy" *ngIf="(configService.config$ | async)?.footerLinks.privacyPolicy">
<a href="{{(configService.config$ | async)?.footerLinks.privacyPolicy}}" target="_blank">Pricacy</a>
</li>
</ul>
我用两个测试写了下面的测试文件
// imports, providers & schemas
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
import { ComponentFixture, TestBed} from '@angular/core/testing';
import { Config } from '@app/shared/models/config';
import { Observable, Subject } from 'rxjs';
import { By } from '@angular/platform-browser';
// services
import { ConfigService } from '@app/shared/services/config.service';
// component page
import { FooterComponent } from './footer.component';
class FakeConfigService {
configSubject = new Subject<Config>();
config$: Observable<Config>;
constructor() {
this.config$ = this.configSubject.asObservable();
}
}
fdescribe('footer-component', () => {
let fixture: ComponentFixture<FooterComponent>;
let fakeConfigService: FakeConfigService;
beforeEach(() => {
fakeConfigService = new FakeConfigService();
TestBed.configureTestingModule({
imports: [],
providers: [
{ provide: ConfigService, useValue: fakeConfigService },
],
declarations: [ FooterComponent ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
}).overrideComponent(FooterComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
fixture = TestBed.createComponent(FooterComponent);
fixture.detectChanges();
});
afterEach(() => {
fakeConfigService.configSubject.unsubscribe();
});
it('Should show the footer links if a footer link is present', () => {
const configSubject: Config = {
allowedGoogleDomains: ['something.com'],
footerLinks: {
manual: 'something.com/manual',
termsAndConditions: 'something.com/termsAndConditions',
privacyPolicy: 'something.com/privacyPolicy'
}
};
fakeConfigService.configSubject.next(configSubject);
fixture.detectChanges();
const footerLinks = fixture.debugElement.query(By.css('#footer-links'));
expect(footerLinks).toBeTruthy();
});
it('Should show the footer manual link if a footer manual link is present', () => {
const configSubject: Config = {
allowedGoogleDomains: ['something.com'],
footerLinks: {
manual: 'something.com/manual',
}
};
fakeConfigService.configSubject.next(configSubject);
fixture.detectChanges();
const footerLinkManual = fixture.debugElement.query(By.css('#footer-link-manual'));
expect(footerLinkManual).toBeTruthy();
});
});
如果填充 configService.config$.footerlinks 后创建了 UL,那么第一个测试就可以通过了。
第二个测试包含手册 link 的 LI 是否失败。当我 console.log fixture.debugElement.query(By.css('#footer-links')) 的 nativeElement 时,它会出现这个
LOG LOG: <ul _ngcontent-a-c1="" id="footer-links"><!--bindings={
"ng-reflect-ng-if": null
}--><!--bindings={
"ng-reflect-ng-if": null
}--><!--bindings={
"ng-reflect-ng-if": null
}--></ul>
因此,无论出于何种原因,测试都会填充 UL 但不会填充连接到 configService.config$.footerlinks.manual 的 LI,即使它们由同一对象填充也是如此。为什么会发生这种情况的任何线索?
应该提到我也尝试使用 fakeAsync/tick() 和 async/fixture.whenStable 并且我得到了相同的结果。任何帮助将不胜感激。
每次模板中有 | async
时,它都会创建一个新的可观察对象订阅。因此,如果您的真实可观察对象是一个 HTTP 可观察对象,它将发送 7 个 HTTP 请求来获取配置。
在你的测试中,当你从你的主题发出并检测到变化时,第一个 ngIf 条件变为真,然后评估里面的条件。但由于主题之前已经发出,他们评估为 false。
您真的需要避免在您的模板中有很多订阅。代码至少应该改成
<ng-container *ngIf="configService.config$ | async as config>
<ul id="footer-links" *ngIf="config.footerLinks">
<li id="footer-link-manual" *ngIf="config.footerLinks.manual">
<a [href]="config.footerLinks.manual" target="_blank">Manual</a>
</li>
<li id="footer-link-terms-and-conditions" *ngIf="config.footerLinks.termsAndConditions">
<a [href]="config.footerLinks.termsAndConditions" target="_blank">Terms</a>
</li>
<li id="footer-link-privacy-policy" *ngIf="config.footerLinks.privacyPolicy">
<a [href]="config.footerLinks.privacyPolicy" target="_blank">Pricacy</a>
</li>
</ul>
</ng-container>
您还应该了解 smart/dumb 模式以避免此类问题并使您的代码更易于测试。
而且模板真的不应该直接从服务访问任何内容。将其封装在您的组件中。