测试异步代码
在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。
Promise
为你的测试返回一个Promise,则Jest会等待Promise的resove状态 如果 Promise 的状态变为 rejected, 测试将会失败。
例如,有一个名为fetchData
的Promise, 假设它会返回内容为'peanut butter'
的字符串 我们可以使用下面的测试代码︰
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Async/Await
或者,您可以在测试中使用 async
和 await
。 写异步测试用例时,可以在传递给test
的函数前面加上async
。 例如,可以用来测试相同的 fetchData
方案︰
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (error) {
expect(error).toMatch('error');
}
});
你也可以将 async
and await
和 .resolves
or .rejects
一起使用。
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
上述示例中,async
and await
实际上是一种基于Promise的异步语法糖。
Be sure to return (or await
) the promise - if you omit the return
/await
statement, your test will complete before the promise returned from fetchData
resolves or rejects.
如果期望Promise被Reject,则需要使用 .catch
方法。 请确保添加 expect.assertions
来验证一定数量的断言被调用。 否则,一个fulfilled状态的Promise不会让测试用例失败。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});
回调
If you don't use promises, you can use callbacks. For example, let's say that fetchData
, instead of returning a promise, expects a callback, i.e. fetches some data and calls callback(null, data)
when it is complete. 你期望返回的数据是一个字符串 'peanut butter'
默认情况下,一旦到达运行上下文底部Jest测试立即结束。 这样意味着这个测试将不能按预期工作。
// 不要这样做!!!
test('the data is peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
问题在于一旦fetchData
执行结束,此测试就在调用回调函数前结束了(因为同步代码结束后,才是异步拿到的数据)。
还有另一种形式的 test
可以解决这个问题。 使用单个参数调用 done
,而不是将测试放在一个空参数的函数。 Jest会等done
回调函 数被调用执行结束后,再结束测试。
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
若 done()
函数从未被调用,测试用例会正如你预期的那样执行失败(显示超时错误)。
若 expect
执行失败,它会抛出一个错误,后面的 done()
不再执行。 若我们想知道测试用例为何失败,我们必须将 expect
放入 try
中,将 error 传递给 catch
中的 done
函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data)
中接收的值。
Jest will throw an error, if the same test function is passed a done()
callback and returns a promise. This is done as a precaution to avoid memory leaks in your tests.
.resolves
/ .rejects
您还可以使用 .resolves
匹配器在您期望的声明,Jest 会等待这一 Promise 来解决。 如果 Promise 被拒绝,则测试将自动失败。
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
一定不要忘记把整个断言作为返回值返回⸺如果你忘了return
语句的话,在 fetchData
返回的这个 promise 变更为 resolved 状态、then() 有机会执行之前,测试就已经被视为已经完成了。
如果你希望Promise返回rejected,你需要使用 .rejects
匹配器。 它和 .resolves
匹配器是一样的使用方式。 如果 Promise 被拒绝,则测试将自动失败。
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
上述几种异步形式没有优劣之分,你可以在你的项目或者文件中混合或单独使用他们。 这只取决于哪种形式更能使您的测试变得简单。