Angular单元测试呼叫读取行为主题

发布于 2025-01-27 05:53:18 字数 3547 浏览 3 评论 0原文

我也有一个Angular类实现的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);
  });

});

这里是组件类

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');
  }
}

这是覆盖范围结果

I have a angular class implemented ControlValueAccessor interface too.

I need to have 100% coverage. Please help me to cover the remain.
Line 20, 35, 36 need to be cover. Tried my best seams I have missed somewhere.

Unit test code

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);
  });

});

Here is the Component 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 here is the coverage result

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

颜漓半夏 2025-02-03 05:53:18

当您监视一种方法时,您将失去实施细节,但在调用该方法时可以访问它,该方法的调用方式以及它被调用了多少次。为了获得世界各地,您必须添加.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();
  }));

When you spy on a method, you lose implementation details but gain access to when it was called, how it was called and how many times it was called. To get the best of both world, you have to add .and.callThrough().

Try the following:

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();
  }));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文