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();
}));
我也有一个 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();
}));