Angular 单元测试调用 readonly BehaviorSubject

Angular unit test calling readonly BehaviorSubject

我也有一个 angular class 实现的 ControlValueAccessor 接口。

我需要 100% 的覆盖率。请帮我把剩下的东西盖上。 第 20、35、36 行需要被覆盖。尝试了我遗漏某处的最佳接缝。

单元测试代码

import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
import { TestSharedModule } from 'src/app/test/test-shared.module';

import { CommentEditorComponent } from './comment-editor.component';

describe('CommentEditorComponent', () => {
  let component: CommentEditorComponent;
  let fixture: ComponentFixture<CommentEditorComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NgbPopoverModule,
        TestSharedModule,
      ],
      declarations: [CommentEditorComponent]
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(CommentEditorComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call writeValue', fakeAsync(() => {
    const savedValue = spyOn(component.savedValue$, 'next');
    component.writeValue('A');
    expect(savedValue).toHaveBeenCalled();
  }));

  it('should call registerOnChange', () => {
    let isCalledOnChange = false;
    const onChange = () => {
      isCalledOnChange = true;
    };
    component.registerOnChange(onChange);
    expect(isCalledOnChange).toBeFalsy();
  });

  it('should call registerOnTouched', () => {
    let isOnTouched = false;
    const onChange = () => {
      isOnTouched = true;
    };
    component.registerOnTouched(onChange);
    expect(isOnTouched).toBeFalsy();
  });

  it('should call setDisabledState', () => {
    const disable = spyOn(component.ctrl, 'disable');
    component.setDisabledState(true);
    expect(disable).toHaveBeenCalled();
    component.setDisabledState(false);
  });

});

这是组件 class

import { EventEmitter, forwardRef } from '@angular/core';
import { Component, Output } from '@angular/core';
import {
  ControlValueAccessor,
  FormControl,
  NG_VALUE_ACCESSOR,
  Validators,
} from '@angular/forms';
import { invoke } from 'lodash-es';
import { BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, map, skip } from 'rxjs/operators';

@Component({
  selector: 'app-comment-editor',
  templateUrl: './comment-editor.component.html',
  styleUrls: ['./comment-editor.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CommentEditorComponent),
      multi: true,
    },
  ],
})
export class CommentEditorComponent implements ControlValueAccessor {
  onTouchedFn: () => void;
  readonly savedValue$ = new BehaviorSubject<string>(null);

  readonly ctrl = new FormControl(null, {
    validators: Validators.required,
  });

  constructor() {
    this.savedValue$.pipe(skip(1)).subscribe((value) => {
      this.ctrl.setValue(value);
      this.setDisabledState(Boolean(value));
    });
  }

  writeValue(val: string): void {
    this.savedValue$.next(val);
  }

  registerOnChange(fn: any): void {
    this.savedValue$.pipe(skip(1), distinctUntilChanged()).subscribe(fn);
  }

  registerOnTouched(fn: any): void {
    this.onTouchedFn = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    invoke(this.ctrl, isDisabled ? 'disable' : 'enable');
  }
}

这是覆盖结果

当你监视一个方法时,你会丢失实现细节,但可以访问它的调用时间、调用方式以及调用次数。为了两全其美,您必须添加 .and.callThrough().

尝试以下操作:

it('should call writeValue', fakeAsync(() => {
    // add .and.callThrough() here so next time savedValue$.next() is called,
    // the actual method is called
    const savedValue = spyOn(component.savedValue$, 'next').and.callThrough();
    component.writeValue('A');
    expect(savedValue).toHaveBeenCalled();
  }));