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
CodePudding user response:
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();
}));