服务中的方法在 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();
}