Reputation: 121
I am trying to write karma test cases for the method inside service but directly getting the service method name is not function error in the test cases.
has anyone faced the same issue?
below is my code.
I have tried to spy the method and return the null observable still the method is coming as undefined in the karma tests.
import { DialogModal } from './dialog.model';
import { Component, OnInit } from '@angular/core';
import { DialogService } from './dialog.service';
declare var $: any;
@Component({
selector: 'app-dialog',
templateUrl: './dialog.component.html',
styleUrls: ['./dialog.component.css']
})
export class DialogComponent implements OnInit {
displayModal: boolean;
dialogDetails: DialogModal;
constructor(public dialogService: DialogService) {
this.subscribeToDialogService();
}
ngOnInit(): void {}
/**
* This function will subscribe for the changes sent by the openDialogModal
* dialogSubject.
*/
subscribeToDialogService(): void {
this.dialogService.getDialogDetails().subscribe(data => {
if (data) {
this.dialogDetails = new DialogModal();
this.dialogDetails = data;
this.displayModal = true;
setTimeout(() => {
if (this.dialogDetails && this.dialogDetails.isAlertBox) {
$('#dialogCompokButton').focus();
} else {
$('#dialogComprejectButton').focus();
}
}, 300);
}
});
}
/**
* This function will be called when the user clicks on the positive response
* in the dialog that appears and in turn will call the dialogConfirmation()
* function to return the promise as true back to the calling component
*/
confirmDialog(): void {
this.displayModal = false;
this.dialogService.dialogConfirmation();
}
/**
* This function will be called when the user clicks on the negative response
* in the dialog that appears and in turn will call the dialogRejection()
* function to return the promise as false back to the calling component
*/
rejectDialog(): void {
this.displayModal = false;
this.dialogService.dialogRejection();
}
}
Service:
import { DialogModal } from './dialog.model';
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class DialogService {
dialogConfirmation: () => void;
dialogRejection: () => void;
dialogSubject: BehaviorSubject<DialogModal> = new BehaviorSubject(undefined);
constructor() {}
/**
* This function is called whenever the user need to display a dialog.
* The user can set the icon, buttons, button labels, dialog message and heading
* The function will return a promise based on the button clicked.
*/
openDialogModal(dialogInputs: DialogModal): Promise<boolean> {
if (dialogInputs)
this.dialogSubject.next({...dialogInputs});
return new Promise<boolean>((resolve, reject) => {
this.dialogConfirmation = () => resolve(true);
this.dialogRejection = () => resolve(false);
});
}
/**
* This function will be called in the dialog component to observe the changes made to
* the behaviour subject and in order to get the latest values of the details to be shown
* in the dialog.
*/
getDialogDetails(): Observable<DialogModal> {
return this.dialogSubject.asObservable();
}
}
Test Cases:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DialogComponent } from './dialog.component';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { DialogService } from './dialog.service';
import { of } from 'rxjs/observable/of';
describe('DialogComponent', () => {
let component: DialogComponent;
let dialogservice: DialogService;
let fixture: ComponentFixture<DialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DialogComponent ],
providers: [DialogService],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
component.confirmDialog();
component.dialogService.dialogConfirmation();
spyOn(dialogservice, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
expect(component.displayModal).toBeFalsy();
expect(component.dialogService.dialogConfirmation).toHaveBeenCalled();
});
});
error:
Upvotes: 0
Views: 1613
Reputation: 1847
Personaly i would not write a unit test for a component, that then tests a service that is used in that component.
I would write directly a unit test for the service. And in the component, i would mock away the service. With that approach my component unit tests are much faster. And they will not break because of an error in the service. Only the service unit tests then will / should break. That makes the error analysis much easier.
But about your problem:
In your code you write:
component.dialogService.dialogConfirmation();
spyOn(dialogservice, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
This contains two flaws.
First flaw is that the method is FIRST invoked, and only later it is changed to call a fake implementation. First you need the spy and redirect the method call, then you can call the method.
Second flaw is, that spyOn expects as a first parameter the object, and then as the second parameter the name of the method. In your code example it does not get an object (the object would be component.dialogService
).
So it should be
spyOn(component.dialogService, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
component.dialogService.dialogConfirmation();
By the way you can shorten the spy to
spyOn(component.dialogService, 'dialogConfirmation').and.return(of(null))
Perhaps one additional thing.
When you subscribe to Observables that will NOT complete by themselves (http calls complete after the first emit), then you should make sure that your component unsubscribes, before it gets destroyed. The reason is, that with "subscribe" you are registering a method to the Observable. That means, even if your component was long ago scrapped, this method will still be alive and will still be executed with each emit.
An easy pattern to unsubscribe is to add all subscriptions into a Subscription object.
private subscriptions: Subscription = new Subscription();
// ...
this.subscriptions.add(
this.myService.myMethod.subscribe( ... )
);
this.subscriptions.add(
this.myOtherService.myOtherMethod.subscribe( ... )
)
ngOnDestroy(){
this.subscriptions.unsubscribe();
}
Upvotes: 2