【问题标题】:Test asynchronous code in Jasmine在 Jasmine 中测试异步代码
【发布时间】:2018-04-05 19:19:14
【问题描述】:

我正在使用 Jasmine 编写一些测试用例。它看起来像这样:

类定义:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return this.firstName + this.lastName;
  }

  getName () {
    setTimeout(() => {
      return this.getFullName();
    }, 100)
  }
}

module.exports = Person;

测试代码:

 const Person = require('./index');

 describe('Test Person methods', () => {
  beforeEach(() => {
    programmer = new Person('John', 'Gray');
    spyOn(programmer, 'getFullName');
    programmer.getName();
  });

  it('getName should call getFullName', () => {
    expect(programmer.getFullName).toHaveBeenCalled();
  })
});

getName 是一个异步函数,它会在一段时间后调用getFullName。由于这种异步特性,it 规范在调用getName 后立即执行,因此测试失败。

我知道可以使用 Jasmine 的done 功能进行异步测试,但是这里不知道如何实现?在文档中,done 方法仅在异步代码中被调用,但测试无法访问该代码,因为它由getName 方法在内部处理。

或者有没有其他不使用done方法的方法?

Here 是此代码的工作示例。

【问题讨论】:

  • 你需要调用done在异步代码被执行,所以你需要改变一个位类Person,以便例如提供一个回调到getName 并在异步代码中调用此回调。
  • @Joel:你能用代码展示一下吗?您也可以在我提供的链接中尝试您的方法。
  • 请注意,如果有承诺会更好看……也许是口味问题
  • @Joel 您已经修改了“getName”方法。我不能这样做,因为我无法更改正在测试的代码的功能。

标签: javascript jasmine karma-jasmine


【解决方案1】:

我认为您需要将done 参数传递给函数并像这样调用它:

beforeEach((done) => {
    someObj = new SomeClass();
    spyOn(someObj, 'onSuccess');
    someObj.start();
    done();
});

it('start should call onSuccess', (done) => {
    expect(someObj.onSuccess).toHaveBeenCalled();
    done();
});

来源:Jasmine Documentation

【讨论】:

  • 嗯,你用的是什么版本的茉莉花?由于文档说 API 在 2.0 版中发生了变化。另外,也许您可​​以创建一个 Codepen/JSFiddle/Codesandbox,这样我就可以自己尝试代码以找到解决方案。
【解决方案2】:

您对getName 的实现是错误的。在 javascript 中,要处理来自异步函数的结果,您需要将 callback 传递给异步函数或返回 Promise。一旦你修复了你的实现,那么测试它应该很简单:

使用回调方法:

getName (callback) {
    setTimeout(() => {
        callback(this.getFullName());
    }, 100)
}

beforeEach(done => {
    ...
    programmer.getName(name => { 
      done(); // at this point the async func. has completed
    });
});

使用承诺:

getName () {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(this.getFullName());
        }, 100)
    }) 
}

beforeEach(done => {
    ...
    programmer
        .getName()
        .then(name => {
            done(); // at this point the async func. has completed
        });
});

编辑:

如果您使用 Promise,您也可以使用 async / await (Jasmine >= 2.7):

beforeEach(async () => {
    ...
    const name = await programmer.getName();
    // async func has completed here. 
    // await acts like .then() and "waits" for your promise to resolve
    // done wouldn't be needed because there aren't any callbacks
});

【讨论】:

  • 我在我的实际代码中使用了异步等待,并在示例代码(在线编译器的链接)中添加了index-async-test.js 文件以反映这一点。不使用done! 测试通过。关于这是如何工作的任何解释?
  • @maverick 查看我的编辑,但基本上 await 正在等待承诺解决,在 beforeEach 退出之前有点“阻塞”,这就是为什么在这种情况下你不需要 done
  • 我在我的实际代码中有相同的async await 实现,但它在那里失败了。即使await 暂停了async 函数,它实际上并没有停止JavaScript 的执行,因此解析器将继续处理下一个代码。当 Promise 解决时,函数执行将恢复。所以在测试用例中,it 块应该在programmer.getName() 之后立即执行,并且测试实际上应该失败。可能是因为不正确的模拟而传入了在线编译器。
  • @maverick 你用的是什么版本的茉莉花? async / await 仅从 2.7 开始支持。文档jasmine.github.io/tutorials/async
  • 我正在使用 Jasmine 3.10.6 和 karma-jasmine 3.10.6
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-03
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2016-12-23
  • 1970-01-01
  • 2012-05-18
相关资源
最近更新 更多