设置模拟服务并使用 jasmine/karma 和 Angular 调用它时出现问题

Problems setting up mock services and calling it with jasmine/ karma and Angular

我有一个调用另一个方法的方法。在该方法中,我订阅了一项服务,如果有匹配项,则使用 Angular 路由器服务进行导航。这是一个stackBlitz example

  goToMain(): void {
    this.viewFunc(this.queryString);
  }

  viewFunc(queryString): void {
    this.reportService.getAnotherReport(queryString).subscribe(x => {
      if (x.label.toLowerCase() === "accept & view") {
        const url = "main";
        this.router.navigate([url], {
          queryParams: { queryString: queryString }
        });
      }
    });
  }

我成功地在我的规范文件中创建了一个测试来测试调用了 viewFunc 方法。然后我尝试从下一个方法中调用的服务模拟订阅。我尝试测试两件事 A. 订阅被称为 B. 如果匹配则导航。

我收到错误“预期间谍 getAnotherReport 已被调用。

  it("should call service", () => {
    const queryString = "10987";
    const testee = component;
    expect(testee).toBeTruthy();

    const routerSpy = { navigate: jasmine.createSpy("navigate") };
    const mySpy = jasmine.createSpy("getAnotherReport").and.callThrough();

    testee.viewFunc(queryString);

    expect(mySpy).toHaveBeenCalled();
    expect(routerSpy.navigate).toHaveBeenCalledWith(["/main"], {
      queryParams: { queryString: queryString }
    });
  });

这是一个stackBlitz example

我还不太了解 jasmine 和单元测试,我怀疑我为我的规范文件设置了不正确的描述/测试平台。因此,如果有人可以帮忙看一下 stackBlitz example,我将不胜感激。

Working StackBlitz.


有几个错别字:

首先,我认为您打算将 getAnotherReport 声明为值,而不是类型:

let getAnotherReport = {
  text: null,
  ref: "7478B45D4D4F1400223BD2F1",
  num: 19,
  urn: "123",
  title: "Test report",
  label: "accept & view"
};

然后,在 SearchComponent.viewFunc 中你有 url = 'main' 并且在你的规范中你有:

expect(routerSpy.navigate).toHaveBeenCalledWith(["/main"], {
  queryParams: { queryString: queryString }
});

应该没有/:

expect(routerSpy.navigate).toHaveBeenCalledWith(["main"], {
  queryParams: { queryString: queryString }
});

现在,让我们看看有趣的东西。

我们将首先探讨第一个断言:

const mySpy = jasmine.createSpy("getAnotherReport").and.callThrough();
/* ... */
testee.viewFunc(queryString);
/* .... */
expect(mySpy).toHaveBeenCalled();

这里的问题与你如何决定监视有关 viewFunc。在本例中,您使用了 spyOn(component, "viewFunc");: 这些是当您调用 spyOn:

时在幕后发生的相关位
var originalMethod = obj[methodName],
spiedMethod = createSpy(methodName, originalMethod),

其中 createSpy 将是一个 empty 间谍,即它将仅用于跟踪函数被调用的次数,参数等。 但是它不会使用原来的实现,这不是你想要的,因为你还有关于ReportService的断言,它驻留在viewFunc的原始实现中。

为了使用初始实现,您可以使用.and.callThrough():

// a quick look at the fact that it uses the original function
SpyStrategy.prototype.callThrough = function() {
  this.plan = this.originalFn;
  return this.getSpy();
};

reportService = TestBed.get(ReportService);
// and here we are using it
spyOn(component, "viewFunc").and.callThrough();

放在一起:

// you can get ahold of the current spy like this
const mySpy = mockReportService.getAnotherReport.and.returnValue(
  of(getAnotherReport)
);

expect(testee).toBeTruthy();
testee.viewFunc(queryString);

expect(mySpy).toHaveBeenCalled();

现在进入第二个断言:

// inside providers array
{ provide: Router, useValue: routerSpy },


/* ... */

const routerSpy = { navigate: jasmine.createSpy("navigate") };

expect(routerSpy.navigate).toHaveBeenCalledWith(["main"], {
  queryParams: { queryString: queryString }
});

在这种情况下,请注意您使用的 与您模拟 Router class 的对象不同

你必须使用你用来模拟有问题的 class 的同一个对象,所以解决这个问题的方法是:

let routerSpy = { navigate: jasmine.createSpy("navigate") };

/* ... */

// inside providers array
{ provide: Router, useValue: routerSpy },

/* ... */

const mySpy = mockReportService.getAnotherReport.and.returnValue(
      of(getAnotherReport)
    );

expect(testee).toBeTruthy();
testee.viewFunc(queryString);

expect(mySpy).toHaveBeenCalled();
expect(routerSpy.navigate).toHaveBeenCalledWith(["main"], {
  queryParams: { queryString: queryString }
});

快速提示

我对 Jasmine 也不是很熟悉,但它帮助我了解了它在幕后的作用。无需了解每一个微小的细节,您至少可以直观地了解如何解决您的问题。因此,在 StackBlitz 应用程序(如您链接的应用程序)中,您可以:

  1. 打开开发工具
  2. CTRL + P 并输入 jasmine.js(可能有多个文件,正确的是包含 callThrough 等已知方法的文件 - 您可以通过按 CTRL + SHIFT + O 并输入方法名称)

此外,您可以搜索 search.component.ts 并在其中放置一些断点,以获得更流畅的调试体验。