JavaScript 单元测试 —— Jest 框架

·  阅读 273

这是我参与更文挑战的第 21 天,活动详情查看:更文挑战

JestFacebook团队构建和维护的JavaScript测试框架,基于Jasmine,是一款优雅、简洁的 Javascript 测试框架。

简单使用

先创建一个项目,并安装 jest

yarn init
yarn add --dev jest

编写一个模块代码和相应的测试用例。

// sum.js
function sum(a, b) {
    return a + b;
}
module.exports = sum;

// sum.test.js   相应的模块测试文件命名为 .test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);  // 断言
});

test 块就是我们的一个测试用例,是测试的最小单位,第一个参数是描述信息,第二个参数是实际执行的函数。在test块的执行函数内,存在断言expect(sum(1, 2)).toBe(3);。所谓断言,是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。一个测试用例里可以有很多断言。

expect(...)返回一个“期望”的值,toBe是相应匹配规则的匹配器。

expect(sum(1,2)) 得到 sum(1.2) 的返回值,根据匹配器 toBe 的规则与所传递的值进行比较,如果符合匹配规则就通过测试,否则抛出错误。

运行命令查看测试结果:

yarn test

匹配器

上面我们使用了 toBe 匹配器,它本质是使用 Object.js 方法对值进行比较。Jest 提供了各种功能的匹配器,详细可以翻看API 文档

我们来了解一下常用的匹配器。

相反的匹配

刚刚提到了toBe 用于判断值是否与预期相等,如果要判断值不等于预期值,可以使用not.toBe(...),如下所示:

test('1 + 1 不等于 3', () => {
    expect(1+1).not.toBe(3);
})

匹配对象

如果要检查对象,不能用toBe,用toEqual,它会递归检查对象或数组的每个字段。它也可以用来匹配其它类型的值。

test('对象比较', () => {
    const data = { one: 1 };
    data['two'] = 2;
    expect(data).toEqual({ one: 1, two: 2 });
});

test('toEqual 匹配各个类型值', () => {
    expect(1 + 1).toEqual(2); 
    expect('test').toEqual('test');
    expect(true).toEqual(true);
    expect(undefined).toEqual(undefined);
})

真值匹配器

Jest 具备以下真值匹配器:

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefinedtoBeUndefined 相反
  • toBeTruthy 匹配任何 if 语句为真
  • toBeFalsy 匹配任何 if 语句为假

如下所示:

test('用 null 进行测试', () => {
    const n = null;  // ✔
    expect(n).toBeNull();  // ✔
    expect(n).toBeDefined();  // ✔
    expect(n).not.toBeUndefined();  // ✔
    expect(n).not.toBeTruthy();  // ✔
    expect(n).toBeFalsy();  // ✔
});

数字匹配器

Jest 也提供了便于判断 number 类型的匹配器,如下所示:

test('two plus two', () => {
    const value = 2 + 2;
    expect(value).toBeGreaterThan(3);
    expect(value).toBeGreaterThanOrEqual(3.5);
    expect(value).toBeLessThan(5);
    expect(value).toBeLessThanOrEqual(4.5);

    // toBe 跟 toEqual 对 number 类型来说是一致的
    expect(value).toBe(4);
    expect(value).toEqual(4);
});

字符串匹配器

字符串匹配器允许使用正则表达式进行匹配,如下所示:

test('单词中没有字母 I', () => {
  expect('team').not.toMatch(/I/);
});

test('单词中存在字符 "stop"', () => {
  expect('Christoph').toMatch(/stop/);
});

数组及可迭代对象

对于数组和可迭代对象,我们可以使用匹配器toContain匹配其中是否存在某个项,如下所示:

const shoppingList = [
    'diapers',
    'kleenex',
    'trash bags',
    'paper towels',
    'milk',
];

test('购物列表中存在 milk', () => {
    expect(shoppingList).toContain('milk');
    expect(new Set(shoppingList)).toContain('milk');
});

异常匹配器

Jest 还提供了Error相关的匹配器,如下所示:

function compileAndroidCode() {
    throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
    expect(() => compileAndroidCode()).toThrow();
    expect(() => compileAndroidCode()).toThrow(Error);

    // 也可以用字符串或正则匹配错误提示语
    expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
    expect(() => compileAndroidCode()).toThrow(/JDK/);
}); 

以上是常用的一些匹配器的介绍,更多Jest匹配器可以查询API 文档

异步代码测试

先来做一个回调函数的用例测试如下:

test('测试异步代码', () => {
    function syncFunc(cb) {
        setTimeout(() => {
            cb(1+2); // 得到的值是 3,不能通过下面的断言
        }, 1000)
    }

    function callBack(data) {
        expect(data).toBe(2);  // 只有为 2 时才通过测试
    }

    syncFunc(callBack);
})

运行会发现测试通过,但是报错了。这是为什么呢?

这是因为Jest默认情况下,代码执行一旦到达执行上下文底部,测试立即结束,不会等待回调函数的执行。

解决方案是使用单个参数调用 done。 Jest会等done回调函数执行结束后,结束测试。如下所示:

test('测试异步代码', (done) => {
    function syncFunc(cb) {
        setTimeout(() => {
            cb(1 + 2);
        }, 1000)
    }

    function callBack(data) {
        try {
            expect(data).toBe(3);
            done();
        } catch(error) {
            done(error);
        }
    }

    syncFunc(callBack);
})

其中,若 expect 执行失败,它会抛出一个错误,后面的 done() 不再执行,如果 done() 函数从未被调用,测试用例会执行失败(显示超时错误)。

若我们想知道测试用例为何失败,我们必须将 expect 放入 try 中,将 error 传递给 catch 中的 done函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data) 中接收的值。

如果是使用 promise,需要 returnpromise,这样 Jest 会等待它执行完毕,如下所示:

test('测试 Promise resolve 执行情况', () => {
    return Promise.resolve(3).then((data) => {
        expect(data).toBe(3);
    })
})

如果期望PromiseReject,则需要使用.catch 方法。

test('测试 Promise reject 执行情况', () => {
    expect.assertions(1); // 如果不加,Promise.resolve(3) 也能通过测试
    return Promise.reject(3).catch((data) => {
        expect(data).toBe(3);
    })
})

其中,添加了 expect.assertions 来验证一定数量的断言被调用。 否则,一个fulfilled状态的Promise不会让测试用例失败。

另外,Jest 还提供了.resolves.rejects 匹配器,使用如下:

test('测试 Promise 执行情况', () => {
    return expect(Promise.resolve(5)).resolves.toBe(5);
})
test('测试 Promise 执行情况', () => {
    return expect(Promise.reject(5)).rejects.toBe(5);
})

另外,我们也可以使用 asyncawait,让异步代码同步执行。可以在传递给test的函数前面加上async,如下:

test('测试异步执行情况', async () => {
    let data = await Promise.resolve(3)
    expect(data).toBe(3);
})

test('测试 Promise 执行情况', async () => {
    await expect(Promise.resolve(5)).resolves.toBe(5);
})

总结

  • 异步代码的测试实现主要是通过让 Jest等待异步代码执行完毕,不要立即结束测试。

如果文章对你有帮助,点个赞呗~

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改