首页 > 解决方案 > Expect jasmine Spy to be called "eventually", before timeout

问题描述

I've written a lot of asynchronous unit tests lately, using a combination of Angular's fakeAsync, returning Promises from async test body functions, the Jasmine done callback, etc. Generally I've been able to make everything work in a totally deterministic way.

A few parts of my code interact in deeply-tangled ways with 3rd party libraries that are very complex and difficult to mock out. I can't figure out a way to hook an event or generate a Promise that's guaranteed to resolve after this library is finished doing background work, so at the moment my test is stuck using setTimeout:

class MyService {
  public async init() {
    // Assume library interaction is a lot more complicated to replace with a mock than this would be
    this.libraryObject.onError.addEventListener(err => {
      this.bannerService.open("Load failed!" + err);
    });
    // Makes some network calls, etc, that I have no control over
    this.libraryObject.loadData();
  }
}

it("shows a banner on network error", async done => {
    setupLibraryForFailure();
    await instance.init();

    setTimeout(() => {
        expect(banner.open).toHaveBeenCalled();
        done();
    }, 500);  // 500ms is generally enough... on my machine, probably
});

This makes me nervous, especially the magic number in the setTimeout. It also scales poorly, as I'm sure 500ms is far longer than any of my other tests take to complete.

What I think I'd like to do, is be able to tell Jasmine to poll the banner.open spy until it's called, or until the test timeout elapses and the test fails. Then, the test should notice as soon as the error handler is triggered and complete. Is there a better approach, or is this a good idea? Is it a built-in pattern somewhere that I'm not seeing?

标签: javascriptunit-testingasynchronousjasmine

解决方案


我认为您可以利用callFake, 基本上在调用此函数后调用另一个函数。

像这样的东西:

it("shows a banner on network error", async done => {
    setupLibraryForFailure();
    // maybe you have already spied on banner open so you have to assign the previous
    // spy to a variable and use that variable for the callFake
    spyOn(banner, 'open').and.callFake((arg: string) => {
      expect(banner.open).toHaveBeenCalled(); // maybe not needed because we are already doing callFake
      done(); // call done to let Jasmine know you're done
    });
    await instance.init();
});

我们在banner.open 上设置了一个间谍,当它被调用时,它将使用callFake 调用回调函数,我们在这个回调中调用done,让Jasmine 知道我们已经完成了我们的断言。


推荐阅读