测试将 class 添加到 angular 中的 <body> 7

Test adding class to the <body> in angular 7

我有这个根 AppComponent 监听服务的变化,然后在 document.body[=22= 上添加或删除 CSS class ]

import { Component, OnInit, Renderer2 } from '@angular/core';
import { SideMenuService } from './core/side-menu/side-menu.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  static readonly sideMenuClass: string = 'side-menu-open';

  constructor(public sideMenuService: SideMenuService, private renderer2: Renderer2) { }

  ngOnInit(): void {
    this.sideMenuService.isOpenChange.subscribe((value: boolean) => {
      if (value) {
        this.renderer2.addClass(document.body, AppComponent.sideMenuClass);
      } else {
        this.renderer2.removeClass(document.body,  AppComponent.sideMenuClass);
      }
    });
  }
}

然后我在我的 *.spec.ts 文件中有这个,其中大部分是我阅读

import { TestBed, async, ComponentFixture, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { CoreModule } from './core/core.module';
import { AppComponent } from './app.component';
import { Renderer2, Type } from '@angular/core';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let app: AppComponent;
  let renderer2: Renderer2;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        CoreModule
      ],
      declarations: [
        AppComponent
      ],
      providers: [Renderer2]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    app = fixture.debugElement.componentInstance;
    //Spy on the renderer
    renderer2 = fixture.componentRef.injector.get<Renderer2>(Renderer2 as Type<Renderer2>);
    spyOn(renderer2, 'addClass').and.callThrough();
 });

  it(`should toggle a class on the <body> tag when opening/closing the side-menu via the side-menu service`, () => {
    app.sideMenuService.open();
    fixture.detectChanges();
    console.log(fixture.debugElement.nativeElement, document.body)
    expect(renderer2.addClass).toHaveBeenCalledWith(jasmine.any(Object), AppComponent.sideMenuClass);
  });
});

但是,现在它给了我错误信息

Expected spy addClass to have been called with [ , 'side-menu-open' ] but it was never called.

我需要做什么才能正确测试此组件?我在这里走对了吗?


编辑:

这是side-menu.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SideMenuService {
  isOpen: boolean = false;

  isOpenChange: Subject<boolean> = new Subject<boolean>();

    constructor()  {
        this.isOpenChange.subscribe((value: boolean) => {
            this.isOpen = value;
        });
    }

  open(): void {
    this.isOpenChange.next(true);
  }
  close(): void {
    this.isOpenChange.next(false);
  }
}

我最终没有使用渲染器并注入了一个我可以在测试中模拟的 window 服务。

在我的 app.module.ts 我有

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [ ... ],
    providers: [
        {provide: 'Window', useValue: window},
        ...
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts中:

import { Component, OnInit, Inject } from '@angular/core';
import { SideMenuService } from './services/side-menu/side-menu.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
    static readonly sideMenuClass: string = 'side-menu-open';

    constructor(@Inject('Window') private window: Window, public sideMenuService: SideMenuService) { }

    ngOnInit(): void {
        this.sideMenuService.isOpen$.subscribe((value: boolean) => {
            if (value) {
                this.window.document.body.classList.add(AppComponent.sideMenuClass);
            } else {
                this.window.document.body.classList.remove(AppComponent.sideMenuClass);
            }
        });
    }
}

神奇的是,我发现 karma-viewport 项目添加了业力测试的能力来修改测试 window(例如响应式屏幕尺寸等) https://github.com/squidfunk/karma-viewport

添加这个允许我在 app.component.spec.ts 中使用 karma testig iframe 作为 window 并且测试通过!

describe('Component: App', () => {
    let fixture: ComponentFixture<AppComponent>;
    let app: AppComponent;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [ ... ],
            declarations: [
                AppComponent
            ],
            providers: [
                {provide: 'Window', useValue: viewport.context.contentWindow},
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        app = fixture.debugElement.componentInstance;
    });

    it(`should toggle a class on the <body> tag when opening/closing the side-menu via the side-menu service`, () => {
        if (viewport.context && viewport.context.contentDocument) {
            const testBody = viewport.context.contentDocument.body;
            fixture.detectChanges(); //do this initially to trigger `ngOnInit()`

            app.sideMenuService.open();
            fixture.detectChanges();
            expect(testBody.className).toContain(AppComponent.sideMenuClass);

            app.sideMenuService.close();
            fixture.detectChanges();
            expect(testBody.className).not.toContain(AppComponent.sideMenuClass);
        } else {
            fail('Could not locate the karma testing iframe document!');
        }
    });

});