I am writing unit test for following class and there are readonly class variables. I need to cover test logic inside that variables.
import { Injectable } from '@angular/core';
import { FormControl } from '@angular/forms';
import { keyBy, flatMap, isBoolean, some, forEach } from 'lodash-es';
import { Observable, forkJoin, ReplaySubject } from 'rxjs';
import {
switchMap,
shareReplay,
finalize,
take,
map,
filter,
withLatestFrom,
tap,
mapTo,
} from 'rxjs/operators';
import {
AffirmationQuestion,
AffirmationData,
AffirmationType,
} from 'src/app/models/affirmation';
import { CorporateGovernanceService } from 'src/app/services/api/corporate-governance.service';
import { environment } from 'src/environments/environment';
const APP_ID = parseInt(localStorage.getItem('id'));
export interface AffirmationRow extends AffirmationQuestion {
readonly section: string;
readonly answer: FormControl;
readonly id: number;
readonly type: AffirmationType;
}
@Injectable({ providedIn: 'root' })
export class AffirmationSectionRepository {
private _reset$ = new ReplaySubject<void>(1);
private _save$: Observable<void>;
constructor(private _service: CorporateGovernanceService) {}
/**
* Questions (config) of affirmations
*/
readonly questions$ = this._reset$.pipe(
switchMap(() => this._service.getAffirmationsConfig()),
shareReplay(1)
);
/**
* Answers (data) of afirmations
*/
readonly answers$ = this._reset$.pipe(
switchMap(() =>
forkJoin([
this.questions$.pipe(take(1)),
this._service.getAffirmationsData(APP_ID),
])
),
map(([affirmationCfgs, data]) => {
const dataById = keyBy(data, (x) => x.affirmationQnsId);
return flatMap(affirmationCfgs, (cfg) =>
cfg.affirmationQns.map((q) => {
const { compalianceStatus: status, affirmationDataId: id } =
dataById[q.affirmationQnsId] ?? {};
return {
...q,
section: cfg.affirmationName,
type: cfg.affirmationType,
answer: new FormControl(status ? status === 'Y' : null),
id: id ?? null,
} as AffirmationRow;
})
);
}),
shareReplay(1)
);
/**
*
* @returns progress observable
*/
save() {
return (
this._save$ ||
(this._save$ = this.answers$.pipe(
take(1),
filter((rows) => some(rows, (x) => x.answer.dirty)),
map((rows) =>
rows
.filter((row) => isBoolean(row.answer.value))
.map(
(row) =>
({
applicationId: APP_ID,
affirmationDataId: row.id,
affirmationQnsId: row.affirmationQnsId,
compalianceStatus: row.answer.value ? 'Y' : 'N',
} as AffirmationData)
)
),
switchMap((data) => this._service.saveAffirmationsData(data)),
withLatestFrom(this.answers$),
tap(([_, rows]) => forEach(rows, (x) => x.answer.markAsPristine())),
mapTo(void 0),
shareReplay(1),
finalize(() => (this._save$ = null))
))
);
}
reset() {
this._reset$.next();
}
}
I need to have 100% coverage. But I can not understand how to cover inside questions$,answers$ and save() function.
Here is the current implementation and result
import { TestBed } from '@angular/core/testing';
import { forkJoin, Observable, of, ReplaySubject } from 'rxjs';
import { AffirmationSectionRepository } from './affirmation-section.repository';
import { CorporateGovernanceService } from 'src/app/services/api/corporate-governance.service';
import { Affirmation, AffirmationType } from 'src/app/models/affirmation';
import { switchMap, shareReplay } from 'rxjs/operators';
const col = [
{ 0: 's', isEditable: false, useGlobalCustomCellView: true },
{ 0: '1', isEditable: false, useGlobalCustomCellView: true },
{ 0: 'd', isEditable: false, useGlobalCustomCellView: true }
];
const affirmation: Affirmation = {
affirmationId: 1,
affirmationName: 'Test name',
isActive: true,
affirmationQns: [],
affirmationType: AffirmationType.Regulation
};
describe('AffirmationSectionRepository', () => {
let service: AffirmationSectionRepository;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: CorporateGovernanceService, useClass: MockCorporateGovernanceService }
]
});
service = TestBed.inject(AffirmationSectionRepository);
});
it('should be created', () => {
(service as any)._reset$ = new ReplaySubject<void>(1);
const mockCorporateGovernanceService = TestBed.inject(CorporateGovernanceService);
spyOn(mockCorporateGovernanceService, 'getAffirmationsConfig').and.callFake(() => {
return of([]);
});
(service as any).questions$ = (service as any)._reset$.pipe(
switchMap(() => mockCorporateGovernanceService.getAffirmationsConfig()),
shareReplay(1)
);
expect((service as any).questions$).toBeTruthy();
expect(service).toBeTruthy();
});
it('should call save method', () => {
(service as any)._save$ = new ReplaySubject<void>(1);
service.save();
expect(service.save()).toEqual((service as any)._save$);
expect(service).toBeTruthy();
});
it('should call reset method', () => {
(service as any)._reset$ = new ReplaySubject<void>(1);
service.reset();
expect(service).toBeTruthy();
});
});
class MockCorporateGovernanceService {
getAffirmationsConfig(): Observable<Affirmation[]> {
return of([]);
}
}
3
1 Answer
You are replacing/overriding the private Subjects/Observables in _reset$
and _save$
(on which questions$
and answers$
depend) after questions$
and answers$
got initialized and before save()
can assign his own implementation (when first called).
So the piped operators are never executed because the emitting Observables are the ones from your test, not the ones created in AffirmationSectionRepository
.
Additional, like Frank mentioned, you then need to mock at least getAffirmationsData()
and saveAffirmationsData()
.
They are probably private and readonly for a reason. You should probably looking at mocking
CorporateGovernanceService
and then calling the publicreset()
instead@FrankFajardo I already have call it is last test case. What is the mistake on that ?
You need to write another unit test for save, where you will not define (service as any)._save$ = new ReplaySubject<void>(1);