在其后面进行单元测试CanDeactiveGuard和ModalDialogService

时间:2020-06-04 21:00:00

标签: angular typescript

我有一个工作正常的 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

2 个答案:

答案 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 触发代码进行单元测试的方法。