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 模式以避免此类问题并使您的代码更易于测试。

而且模板真的不应该直接从服务访问任何内容。将其封装在您的组件中。