Jest怎么测试异步代码
在上一次我们已经了解到怎么使用test + expect + 匹配器进行简单测试, 但是距离我们真正的能够在工作中运用还有一个关键点, 那就是异步代码的测试.
让我们来看看, 异步代码能不能像普通代码一样测试
test("测试异步代码", () => {
setTimeout(() => {
throw new Error();
});
});
//测试通过
很明显, 普通的写法并不能满足测试异步.
测试异步的三种方式
在Jest的官方文档中, 为我们列举了两种测试异步代码的方式
对于Promise
假设有一个名为fetchData的Promise, 假设它会返回内容为'peanut butter'的字符串 我们可以使用下面的测试代码
方法一: return+promise︰
test('the data is peanut butter', () => {
expect.assertions(1)
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
这里的关键是 return, 你必须明确的返回一个promise, 那么test函数会自动等待promise的状态改变后才结束执行.
方法二: async/await
test接收的回调函数可以是一个async函数, 在这种情况下, test会自动等待函数执行完毕才结束执行
test('the data is peanut butter', async () => {
expect.assertions(1)
const data = await fetchData();
expect(data).toBe('peanut butter');
});
对于回调函数
test的回调函数可以接收一个done参数, 这个done是一个函数, 在done被调用之前, test不会结束执行
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);
});
Tips
- 关于
expect.assertions(1), 这段代码断言了测试里进行了1次expect断言, 用来保证异步函数被正确测试 - 在
return Promise或者调用done之前, 测试代码不会结束执行, 那么如果我们return一个不会改变的状态的Promise或者不去调用done呢?
test("测试异步代码Promise", () => {
return new Promise(() => {});
});
//thrown: "Exceeded timeout of 5000 ms for a testfalse.
test("测试异步代码回调", (done) => {
expect.assertions(1);
setTimeout(() => {
expect(1).toBe(1);
});
});
//thrown: "Exceeded timeout of 5000 ms for a testfalse.
由此可见, test会在等待5秒后报错
- 在使用done回调的方式解决异步测试时需要注意, done在被调用之前, 即便代码报错也不会结束测试, 所以需要手动捕获错误
手写test函数
首先明确需求
- test接收两个参数, 测试描述和测试回调
- 当测试回调return一个promise时, test应等待promise状态更改
- 当测试回调是一个async函数时, test应等待函数执行完毕
- 当测试回调接收一个参数时, test应在该参数被调用前等待
- test最长等待5s, 超时则报错
type TestType = [Parameters<typeof test>[0], Parameters<typeof test>[1]];
class TestManager {
private tests: Array<TestType> = [];
private static instance = new TestManager();
private constructor() {}
static getInstance() {
return this.instance;
}
addTest(test: TestType) {
this.tests.push(test);
}
private createDone(resolve: (value: unknown) => void) {
return () => {
resolve("测试");
};
}
private waitTimer() {
return setTimeout(() => {
throw new Error("等待超时");
}, 5000);
}
async callTests() {
for (let i = 0; i < this.tests.length; i++) {
const callbackFn = this.tests[i][1];
const timer = this.waitTimer();
/**当回调函数接收done参数时 */
if (callbackFn.length >= 1) {
const callbackPromise = new Promise((resolve) => {
this.tests[i][1](this.createDone(resolve));
});
await callbackPromise;
} else {
await (callbackFn as () => Promise<any>)();
}
clearTimeout(timer);
}
}
}
const test = (
describe: string,
callback: ((done: () => void) => void) | (() => Promise<any>) | (() => void)
) => {
TestManager.getInstance().addTest([describe, callback]);
};
以上就是我的简单实现, 让我们来测试一下
test("async1", () => {
return new Promise((resolve) => {
resolve(1);
}).then((val) => {
console.log(val);
});
});
test("sync1", () => {
console.log(2);
});
test("async2", async () => {
const val = await new Promise((resolve) => {
resolve(3);
});
console.log(val);
});
test("sync2", () => {
console.log(4);
});
test("async3", (done) => {
setTimeout(() => {
console.log(5);
done();
});
});
test("sync3", () => {
console.log(6);
});
TestManager.getInstance().callTests();
// 1 2 3 4 5 6
ok, 符合要求. 那么我们就先告一段落了