服务中的方法在 angular 5 个带有业力的单元测试中不起作用
method inside service is not function in angular 5 unit tests with karma
我正在尝试为服务内部的方法编写业力测试用例,但在测试用例中直接获取服务方法名称不是函数错误。
有没有人遇到同样的问题?
下面是我的代码。
我已经尝试监视方法和 return 可观察到的 null 方法仍然在业力测试中未定义。
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();
}
}
服务:
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();
}
}
测试用例:
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();
});
});
错误:
我个人不会为组件编写单元测试,然后测试该组件中使用的服务。
我会直接为服务编写单元测试。在组件中,我会模拟服务。使用这种方法,我的组件单元测试要快得多。而且它们不会因为服务错误而中断。只有服务单元测试才会/应该中断。这使得错误分析变得更加容易。
但是关于你的问题:
在你的代码中你写:
component.dialogService.dialogConfirmation();
spyOn(dialogservice, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
这包含两个缺陷。
第一个缺陷是首先调用该方法,然后才将其更改为调用伪造的实现。首先你需要间谍和重定向方法调用,然后你可以调用方法。
第二个缺陷是,spyOn 期望将对象作为第一个参数,然后将方法名称作为第二个参数。在您的代码示例中,它没有获得对象(该对象将是 component.dialogService
)。
所以应该是
spyOn(component.dialogService, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
component.dialogService.dialogConfirmation();
顺便说一句,你可以将间谍缩短为
spyOn(component.dialogService, 'dialogConfirmation').and.return(of(null))
也许还有一件事。
当您订阅不会自行完成的 Observables(http 调用在第一次发出后完成)时,您应该确保您的组件在它被销毁之前取消订阅。原因是,使用 "subscribe" 您正在向 Observable 注册一个方法。这意味着,即使你的组件很久以前就被废弃了,这个方法仍然存在,并且仍然会在每次发出时执行。
一种简单的取消订阅模式是将所有订阅添加到订阅对象中。
private subscriptions: Subscription = new Subscription();
// ...
this.subscriptions.add(
this.myService.myMethod.subscribe( ... )
);
this.subscriptions.add(
this.myOtherService.myOtherMethod.subscribe( ... )
)
ngOnDestroy(){
this.subscriptions.unsubscribe();
}
我正在尝试为服务内部的方法编写业力测试用例,但在测试用例中直接获取服务方法名称不是函数错误。
有没有人遇到同样的问题?
下面是我的代码。
我已经尝试监视方法和 return 可观察到的 null 方法仍然在业力测试中未定义。
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();
}
}
服务:
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();
}
}
测试用例:
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();
});
});
错误:
我个人不会为组件编写单元测试,然后测试该组件中使用的服务。
我会直接为服务编写单元测试。在组件中,我会模拟服务。使用这种方法,我的组件单元测试要快得多。而且它们不会因为服务错误而中断。只有服务单元测试才会/应该中断。这使得错误分析变得更加容易。
但是关于你的问题:
在你的代码中你写:
component.dialogService.dialogConfirmation();
spyOn(dialogservice, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
这包含两个缺陷。
第一个缺陷是首先调用该方法,然后才将其更改为调用伪造的实现。首先你需要间谍和重定向方法调用,然后你可以调用方法。
第二个缺陷是,spyOn 期望将对象作为第一个参数,然后将方法名称作为第二个参数。在您的代码示例中,它没有获得对象(该对象将是 component.dialogService
)。
所以应该是
spyOn(component.dialogService, 'dialogConfirmation').and.callFake(() => {
return of(null);
});
component.dialogService.dialogConfirmation();
顺便说一句,你可以将间谍缩短为
spyOn(component.dialogService, 'dialogConfirmation').and.return(of(null))
也许还有一件事。
当您订阅不会自行完成的 Observables(http 调用在第一次发出后完成)时,您应该确保您的组件在它被销毁之前取消订阅。原因是,使用 "subscribe" 您正在向 Observable 注册一个方法。这意味着,即使你的组件很久以前就被废弃了,这个方法仍然存在,并且仍然会在每次发出时执行。
一种简单的取消订阅模式是将所有订阅添加到订阅对象中。
private subscriptions: Subscription = new Subscription();
// ...
this.subscriptions.add(
this.myService.myMethod.subscribe( ... )
);
this.subscriptions.add(
this.myOtherService.myOtherMethod.subscribe( ... )
)
ngOnDestroy(){
this.subscriptions.unsubscribe();
}