Jest学习文档(二)

248 阅读3分钟

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

  1. 关于expect.assertions(1), 这段代码断言了测试里进行了1次expect断言, 用来保证异步函数被正确测试
  2. 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秒后报错

  1. 在使用done回调的方式解决异步测试时需要注意, done在被调用之前, 即便代码报错也不会结束测试, 所以需要手动捕获错误

手写test函数

首先明确需求

  1. test接收两个参数, 测试描述和测试回调
  2. 当测试回调return一个promise时, test应等待promise状态更改
  3. 当测试回调是一个async函数时, test应等待函数执行完毕
  4. 当测试回调接收一个参数时, test应在该参数被调用前等待
  5. 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, 符合要求. 那么我们就先告一段落了