我有一个工作正常的 CanDeactiveGuard 。我正在编写单元测试,它们正在工作,除了要调用的单元测试并启动提示用户保存的“模态对话框”组件。 openConfirmDialog()方法位于Guard中,但它调用服务来启动模式。该服务是我要执行的代码。
因此,有Guard类和 ModalDialogService 负责启动模式。从Guard中的方法执行 ModalDialogService 中的代码是否现实?还是应该单独测试服务?
以下是防护说明文件:
class MockGuardComponent implements ComponentCanDeactivate {
// Set this value to the value you want to mock being returned from
GuardedComponent
returnValue: boolean | Observable<boolean>;
canDeactivate(): boolean | Observable<boolean> {
return this.returnValue;
}
}
describe('PendingChangesGuard', () => {
let mockGuardComponent: MockGuardComponent;
let service: PendingChangesGuard;
let dialogService: ModalDialogService;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ModalDialogComponent],
providers: [
PendingChangesGuard,
MockGuardComponent,
Overlay,
ModalDialogService,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [ModalDialogComponent],
},
});
service = TestBed.get(PendingChangesGuard);
dialogService = TestBed.get(ModalDialogService);
mockGuardComponent = TestBed.get(MockGuardComponent);
});
it('should expect service to instantiate', () => {
expect(service).toBeTruthy();
});
it('can route if unguarded -- form is not dirty', () => {
mockGuardComponent.returnValue = true;
expect(service.canDeactivate(mockGuardComponent)).toBeTruthy();
});
it('cannot route if guarded -- form is dirty', () => {
// *** Here is where I'm trying to execute the openConfirmDialog()
method that creates a modal
spyOn(service, 'openConfirmDialog').and.callThrough();
mockGuardComponent.returnValue = false;
expect(service.canDeactivate(mockGuardComponent)).toBeFalsy();
expect(service.openConfirmDialog).toHaveBeenCalled();
});
it('will route if guarded and user accepted the dialog and
confirmed', () => {
// Mock the behavior of the Component
const subject$ = new Subject<boolean>();
mockGuardComponent.returnValue = subject$.asObservable();
const canDeactivate$ = <Observable<boolean>>(
service.canDeactivate(mockGuardComponent)
);
canDeactivate$.subscribe((deactivate) => {
// This is the real test
expect(deactivate).toBeTruthy();
});
// Emulate the accept action
subject$.next(true);
});
it('will not route if guarded and user rejected the dialog', () => {
// Mock the behavior of the MockGuardedComponent
const subject$ = new Subject<boolean>();
mockGuardComponent.returnValue = subject$.asObservable();
const canDeactivate$ = <Observable<boolean>>(
service.canDeactivate(mockGuardComponent)
);
canDeactivate$.subscribe((deactivate) => {
// this is the real test
expect(deactivate).toBeFalsy();
});
// Emulate the reject
subject$.next(false);
});
});
因此,您可以看到这是我正在尝试通过 openConfirmDialog()方法直接调用的第二项测试,并启动了模式。
这是 ModalDialogService 类:
export class ModalDialogService {
constructor(
@Inject(DOCUMENT) private readonly document: Document,
private readonly viewportRuler: ViewportRuler,
private readonly injector: Injector,
private readonly overlay: Overlay
) {}
public open<T, D>(
component: ComponentType<T>,
config: ModalDialogConfig<D> = {}
): ModalDialogRef<T> {
// Override default configuration
const dialogConfig = { ...DEFAULT_CONFIG, ...config };
// Returns an OverlayRef which is a PortalHost
const overlayRef = this.createOverlay(dialogConfig);
// Instantiate remote control
const dialogRef = new ModalDialogRef<T>(overlayRef);
dialogRef.instance = this.attachDialogContainer(
component,
overlayRef,
dialogConfig,
dialogRef
);
if (dialogConfig.hasBackdrop && dialogConfig.closeOnBackdropClick)
{
overlayRef.backdropClick().subscribe(() => dialogRef.close());
}
return dialogRef;
}
private createOverlay(config: ModalDialogConfig): OverlayRef {
const overlayConfig = this.getOverlayConfig(config);
return this.overlay.create(overlayConfig);
}
private attachDialogContainer<T>(
component: ComponentType<T>,
overlayRef: OverlayRef,
config: ModalDialogConfig,
dialogRef: ModalDialogRef<T>
): T {
const injector = this.createInjector(config, dialogRef);
const containerPortal = new ComponentPortal(component, undefined,
injector);
const containerRef: ComponentRef<T> =
overlayRef.attach(containerPortal);
return containerRef.instance;
}
private createInjector<T, D>(
config: ModalDialogConfig<D>,
dialogRef: ModalDialogRef<T>
): PortalInjector {
const injectionTokens = new WeakMap();
injectionTokens.set(ModalDialogRef, dialogRef);
injectionTokens.set(MODAL_DIALOG_DATA, config.data || null);
return new PortalInjector(this.injector, injectionTokens);
}
private getOverlayConfig(config: ModalDialogConfig): OverlayConfig {
const positionStrategy = this.overlay
.position()
.global()
.centerHorizontally()
.centerVertically();
const overlayConfig = new OverlayConfig({
hasBackdrop: config.hasBackdrop,
backdropClass: config.backdropClass,
panelClass: config.panelClass,
disposeOnNavigation: config.closeOnNavigation,
scrollStrategy: this.overlay.scrollStrategies.block(),
positionStrategy,
});
return overlayConfig;
}
}
由于 openConfirmDialog()方法实际上并未执行 ModalDialogService 中的代码,因此我必须分别进行测试,对吗?而且,如果是这种情况,我是否需要模拟创建对话框所需的所有对象,即 OverlayRef,ModalDialogConfig,ModalDialogRef ?
答案 0 :(得分:1)
据我了解: 是的,您必须为进行单元测试的 ModalDialogService 创建一个测试用例。因此每个文件都必须有测试文件和相应的测试用例。
关于下面的测试用例,以及提供程序中的 ModalDialogService 。您已经导入了该服务而不是该服务的模拟(就像我们这样做的提供程序:[{提供:ModalDialogService,useValue:{与服务相关的yourMockvalues}}])。
dialogService = TestBed.get(ModalDialogService);
//您正在使用间谍,因为您在期望部分中也需要该东西,所以它不会影响服务功能。
因此,当您监视某个方法时,不会调用实际方法。
it('cannot route if guarded -- form is dirty', () => {
// *** Here is where I'm trying to execute the openConfirmDialog()
method that creates a modal
spyOn(service, 'openConfirmDialog').and.callThrough(); // not actual will hit
mockGuardComponent.returnValue = false;
expect(service.canDeactivate(mockGuardComponent)).toBeFalsy();
expect(service.openConfirmDialog).toHaveBeenCalled(); // to use in inspect you need to spy.
});
这就像您已经测试了一个部分,在停用'openConfirmDialog'时将调用该部分。 并且会出现一个其他测试用例。当您调用“ openConfirmDialog”时会发生什么。
仅模拟该组件中正在使用的方法/属性。。
类似于ModalDialogService可以有5种方法,但是在PendingChangesGuard中,您仅使用了2种ModalDialogService方法。
因此在PendingChangesGuard中,仅模拟ModalDialogService的2种方法。
providers:[{ provide:ModalDialogService , useValue: {open: jasmine.createSpy('open')} }]
答案 1 :(得分:0)
我认为最好在自己的spec文件中测试该服务,并使用以下代码创建了一个简单的单元测试:
it('should open a modal dialog', () => {
spyOn(modalDialogService, 'open').and.callThrough();
modalDialogService.open(ModalDialogComponent, null);
expect(modalDialogService.open).toHaveBeenCalled();
});
我不必模拟 ModalDialogService 的任何依赖关系,我只需要将 ModalDialogComponent 传递给 open 触发代码进行单元测试的方法。