异步工作

在现代 Javascript 应用中,异步代码非常常见。它的测试方式与同步代码的测试方式大多相同,但有一个关键区别:Jasmine 需要在异步工作完成时收到通知。

Jasmine 支持 3 种管理异步工作的方法:async/await、Promise 和回调。如果 Jasmine 未检测到其中一种,则它会假定该工作为同步工作,并且在函数返回后立即开始处理队列中的下一项工作。所有这些机制都适用于 beforeEachafterEachbeforeAllafterAllit

async/await

通常,编写异步测试的最便捷方法是使用 async/awaitasync 函数隐式返回一个 Promise。在处理队列中的下一项工作之前,Jasmine 会等到返回的 Promise 被解析或拒绝。拒绝的 Promise 会导致规范失败,或者在 beforeAllafterAll 的情况下导致套件级失败。

beforeEach(async function() {
  await someLongSetupFunction();
});

it('does a thing', async function() {
  const result = await someAsyncFunction();
  expect(result).toEqual(someExpectedValue);
});

Promise

如果你需要更多控制权,你可以显式返回一个 Promise。Jasmine 认为任何具有 then 方法的对象都是一个 Promise,因此你可以使用 Javascript 运行时内置的 Promise 类型或一个库。

beforeEach(function() {
  return new Promise(function(resolve, reject) {
    // do something asynchronous
    resolve();
  });
});

it('does a thing', function() {
  return someAsyncFunction().then(function (result) {
    expect(result).toEqual(someExpectedValue);
  });
});

回调

还可以使用回调编写异步测试。这是一种较低级别机制,并且往往更容易出错,但对于测试基于回调的代码或难以用 Promise 表达的测试,它可能很有用。如果传递给 Jasmine 的函数带有一个参数(传统上称为 done),那么 Jasmine 将传递一个函数,在异步工作完成后调用该函数。

done 回调至关重要,它必须被精确调用一次,而且必须让 done 调用成为异步函数或其调用的任何函数执行的最后一项工作。在编写回调式异步测试时,一个常见的错误是在被测代码仍在运行时调用 done。在这种情况下,在调用 done 之后引发的错误可能会与导致错误的规范相关联,甚至可能根本没有报告。

beforeEach(function(done) {
  setTimeout(function() {
    // do some stuff
    done();
  }, 100);
});


it('does a thing', function(done) {
  someAsyncFunction(function(result) {
    expect(result).toEqual(someExpectedValue);
    done();
  });
});

处理故障

有时某些内容在您的异步代码中不起作用,并且您希望您的规范正确失败。任何未处理的错误均由 Jasmine 捕获,并发送到当前正在执行的规范。有时您需要显式让您的规范失败。

使用 Promise 失败

拒绝的 Promise 会导致规范失败,就像抛出错误一样。

beforeEach(function() {
  return somePromiseReturningFunction();
});

it('does a thing', function() {
  // Since `.then` propagates rejections, this test will fail if
  // the promise returned by asyncFunctionThatMightFail is rejected.
  return asyncFunctionThatMightFail().then(function(value) {
    // ...
  });
});

function somePromiseReturningFunction() {
  return new Promise(function(resolve, reject) {
    if (everythingIsOk()) {
      resolve();
    } else {
      reject();
    }
  });
}

使用 async/await 失败

async/await 函数可以通过返回拒绝的 Promise 或抛出错误来表示失败。

beforeEach(async function() {
  // Will fail if the promise returned by
  // someAsyncFunction is rejected.
  await someAsyncFunction();
});

it('does a thing', async function() {
  // Will fail if doSomethingThatMightThrow throws.
  doSomethingThatMightThrow();

  // Will fail if the promise returned by
  // asyncFunctionThatMightFail is rejected.
  const value = await asyncFunctionThatMightFail();
  // ...
});

使用回调失败

作为回调传递的 done 函数也可以用于通过使用 done.fail() 来让规范失败,可以选择传递消息或 Error 对象。

beforeEach(function(done) {
  setTimeout(function() {
    try {
      riskyThing();
      done();
    } catch (e) {
      done.fail(e);
    }
  });
});

done 函数还会检测直接传递给它的 Error 以导致规范失败。

beforeEach(function(done) {
  setTimeout(function() {
    let err = null;

    try {
      riskyThing();
    } catch (e) {
      err = e;
    }

    done(err);
  });
});

报告程序

报告程序事件处理程序也可以通过上述任何一种方法使用异步。请注意,所有报告程序事件都已经接收数据,因此如果您使用回调方法,则 done 回调应为最后一个参数。

使用模拟时钟避免编写异步测试

如果一项操作是异步的,仅仅因为它依赖于 setTimeout 或其他基于时间的行为,一种很好的测试方式是使用 Jasmine 的 模拟时钟,使其同步运行。这种类型的测试可能更便于编写,并且速度快于实际等待时间流逝的异步测试。

function doSomethingLater(callback) {
  setTimeout(function() {
    callback(12345);
  }, 10000);
}

describe('doSomethingLater', function() {
  beforeEach(function() {
    jasmine.clock().install();
  });

  afterEach(function() {
    jasmine.clock().uninstall();
  });

  it('does something after 10 seconds', function() {
    const callback = jasmine.createSpy('callback');
    doSomethingLater(callback);
    jasmine.clock().tick(10000);
    expect(callback).toHaveBeenCalledWith(12345);
  });
});