I have a rather large service method that I am trying to test. I cannot change this method at the moment.
downloadRow(event: any, cell: any, mult_download: boolean): void {
const maxDownloadSize = this.propsService.getProperty('maxDownloadSize');
if (!mult_download) {
const observationSize = cell.getData().file_size;
const observation_oid = cell.getData().observation_oid;
console.log(mult_download);
console.log(maxDownloadSize);
console.log(observationSize);
console.log(observation_oid);
debugger;
// async job for retrieval if >=2GB
if (observationSize / maxDownloadSize >= 1) {
console.log("HERE");
const bodyDialog = this.body_async.replace(
'@size@',
this.conversionsService.formatFileSize(observationSize),
);
this.dialogService.open(ConfirmDialogComponent, {
context: {
header: this.header,
body: bodyDialog,
},
hasBackdrop: false,
hasScroll: true,
})
.onClose.subscribe((confirm) =>
this.doAsyncDownloadFile(confirm, observation_oid),
);
// direct download
} else {
this.doDownloadFile(true, observation_oid);
}
// multiple download cell has an array of objects(rows to download)
} else if (mult_download) {
// console.log('downloadRow mult_download');
let totsiz = 0;
let jobType = false;
const list_of_observationOids = [];
let num_files = 0;
for (const row in cell) {
if ({}.hasOwnProperty.call(cell, row)) {
totsiz = cell[row].file_size;
list_of_observationOids.push(`${cell[row].observation_oid} \n`.trim());
num_files ;
}
}
// console.log('downloadRow mult_download ' String(list_of_observationOids));
if (totsiz / maxDownloadSize >= 1) {
jobType = true;
}
if (jobType) {
console.log("HERE AGAIN1")
const bodyDialog = this.body_mult_files.replace(
'@files@',
String(list_of_observationOids),
);
this.dialogService.open(ConfirmDialogComponent, {
context: {
header: this.header_mult.replace(
'@num_files@',
String(num_files)),
body: bodyDialog,
body_mult: this.body_mult,
},
hasBackdrop: false,
hasScroll: true,
})
.onClose.subscribe((confirm) =>
this.doAsyncMultDownloadFile(confirm, list_of_observationOids),
);
} else if (!jobType) {
console.log("HERE AGAIN")
this.doSyncMultDownloadFile(true, list_of_observationOids);
}
}
}
This is the test. The simplest case is to check the dialog is opened for the first if branch.
import { SsaQueriesHelperService } from './ssa-queries-helper.service';
import { AppTabulatorService } from './app-tabulator.service';
import { ConversionsService } from './conversions.service';
import { ConfirmDialogComponent } from '@shared/components/confirm-dialog.component';
import {
NbToastrService,
NbDialogService
} from '@nebular/theme';
import { PropertiesService } from './properties.service';
class DataRow {
getData() {
return { file_size: 3000000000, observation_oid: 194187682 };
}
}
let service: AppTabulatorService;
let dialogService: jasmine.SpyObj<NbDialogService>;
let propsService: jasmine.SpyObj<PropertiesService>;
let conversionService: jasmine.SpyObj<ConversionsService>;
describe('AppTabulatorService', () => {
const toastrServiceSpy = jasmine.createSpyObj('NbToastrService', ['show']);
const dialogServiceSpy = jasmine.createSpyObj('NbDialogService', ['open']);
const queriesHelperSpy = jasmine.createSpyObj('SsaQueriesHelperService', [
'createPostcardDownloadUrl',
'createHighResolutionPostcardDownloadUrl',
'timeSeriesQuery',
'createProductDownloadUrl'
]);
const conversionsServiceSpy = jasmine.createSpyObj('ConversionsService', ['formatFileSize']);
const propsServiceSpy = jasmine.createSpyObj('PropertiesService', ['getProperty']);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AppTabulatorService,
{ provide: NbToastrService, useValue: toastrServiceSpy },
{ provide: NbDialogService, useValue: dialogServiceSpy },
{ provide: SsaQueriesHelperService, useValue: queriesHelperSpy },
{ provide: ConversionsService, useValue: conversionsServiceSpy },
{ provide: PropertiesService, useValue: propsServiceSpy },
]
}).compileComponents();
service = TestBed.inject(AppTabulatorService);
dialogService = TestBed.inject(NbDialogService) as jasmine.SpyObj<NbDialogService>;
propsService = TestBed.inject(PropertiesService) as jasmine.SpyObj<PropertiesService>;
conversionService = TestBed.inject(ConversionsService) as jasmine.SpyObj<ConversionsService>;
});
it("should create", () => {
expect(service).toBeTruthy();
});
it("should NOT download large file from row", () => {
propsService.getProperty.and.returnValue(3000000000);
const subSpy = jasmine.createSpy('Subscription');
const observableSpy = jasmine.createSpyObj('Observable', ['subscribe']);
observableSpy.subscribe.and.returnValue( subSpy );
const dialogRefSpy = jasmine.createSpyObj('NbDialogRef', ['onClose']);
dialogRefSpy.onClose.and.returnValue(observableSpy);
dialogService.open.and.returnValue(new ConfirmDialogComponent(dialogRefSpy));
const row = new DataRow();
service.downloadRow(null, row, false);
expect(conversionService.formatFileSize).toHaveBeenCalledWith("abc");
expect(dialogService.open.calls.count()).toBe(1);
});
});
It always fails with:
HeadlessChrome 98.0.4758 (Mac OS X 10.15.7) AppTabulatorService should NOT download large file from row FAILED
TypeError: Cannot read properties of undefined (reading 'subscribe')
at AppTabulatorService.downloadRow (http://localhost:9876/_karma_webpack_/src/app/core/services/app-tabulator.service.ts:244:24)
I've understood I've correctly stubbed the dialog service and chain of method calls? Really I just want to verify we call open with the correct arguments.
I also tried:
const observableSpy = jasmine.createSpyObj('Observable', ['subscribe']);
observableSpy.subscribe.and.returnValue( data => {false} );
I also have the sensation the test could be simplified? Is it really necessary to create all these spy objects.
Thanks for any help.
CodePudding user response:
Try this (please follow comments with !!):
let service: AppTabulatorService;
// !! assign these properties to the createSpyObj
let toastrService: jasmine.SpyObj<NbToastrService>;
let dialogService: jasmine.SpyObj<NbDialogService>;
let queriesHelper: jasmine.SpyObj<SsaQueriesHelperService>;
let propsService: jasmine.SpyObj<PropertiesService>;
let conversionService: jasmine.SpyObj<ConversionsService>;
describe('AppTabulatorService', () => {
beforeEach(() => {
// !! assign all of these properties in a beforeEach so you have
// fresh spies for every test
toastrService = jasmine.createSpyObj('NbToastrService', ['show']);
dialogService = jasmine.createSpyObj('NbDialogService', ['open']);
queriesHelper = jasmine.createSpyObj('SsaQueriesHelperService', [
'createPostcardDownloadUrl',
'createHighResolutionPostcardDownloadUrl',
'timeSeriesQuery',
'createProductDownloadUrl'
]);
conversionsService = jasmine.createSpyObj('ConversionsService',
['formatFileSize']);
propsService = jasmine.createSpyObj('PropertiesService', ['getProperty']);
TestBed.configureTestingModule({
providers: [
AppTabulatorService,
{ provide: NbToastrService, useValue: toastrServiceSpy },
{ provide: NbDialogService, useValue: dialogServiceSpy },
{ provide: SsaQueriesHelperService, useValue: queriesHelperSpy },
{ provide: ConversionsService, useValue: conversionsServiceSpy },
{ provide: PropertiesService, useValue: propsServiceSpy },
]
}).compileComponents();
service = TestBed.inject(AppTabulatorService);
// !! get rid of the below lines, we will only have handles on the mocks/stubs and not the actual services
/* dialogService = TestBed.inject(NbDialogService) as jasmine.SpyObj<NbDialogService>;
propsService = TestBed.inject(PropertiesService) as jasmine.SpyObj<PropertiesService>;
conversionService = TestBed.inject(ConversionsService) as jasmine.SpyObj<ConversionsService>; */
});
it("should create", () => {
expect(service).toBeTruthy();
});
it("should NOT download large file from row", () => {
propsService.getProperty.and.returnValue(3000000000);
const subSpy = jasmine.createSpy('Subscription');
// !! Get rid of the below lines
/* const observableSpy = jasmine.createSpyObj('Observable', ['subscribe']);
observableSpy.subscribe.and.returnValue( subSpy );
const dialogRefSpy = jasmine.createSpyObj('NbDialogRef', ['onClose']);
dialogRefSpy.onClose.and.returnValue(observableSpy); */
// !! make dialog open method return an object with onClose property
// directly assigned to an observable value
dialogService.open.and.returnValue({ onClose: of(true) });
/* !! get rid of below line
dialogService.open.and.returnValue(new ConfirmDialogComponent(dialogRefSpy)); */
const row = new DataRow();
service.downloadRow(null, row, false);
expect(conversionService.formatFileSize).toHaveBeenCalledWith("abc");
// !! below expectation won't work anymore, maybe expect that
// this.doAsyncDownloadFile(confirm, observation_oid) was called
// expect(dialogService.open.calls.count()).toBe(1);
});
});
The above might not be a complete answer but it should hopefully get you unblocked.