01 前端测试框架Jest入门

321 阅读8分钟

Jest入门

简介

Jest是facebook推出的一款优雅、简洁的 JavaScript 测试框架, 它支持 Babel、TypeScript、Node、React、Angular、Vue 等诸多框架!

在官方网站中,展示了它以下几个优点:

  1. 无需配置:Jest 的目标是在大多数 JavaScript 项目中即装即用,无需配置。

  2. 快照:轻松编写持续追踪大型对象的测试,并在测试旁或代码内显示实时快照。

  3. 隔离的:并行进行测试,发挥每一丝算力。

  4. 优秀接口:从 it 到 expect - Jest 将工具包整合在一处。文档齐全、不断维护。

除此之外,Jest还有几个独特的功能,让测试体验更佳。

安全快速

确保您的测试具有独一无二的全局状态,Jest 才能可靠地并行测试。 为了缩短测试时间,Jest 会优先运行未通过的测试,并根据每个测试的时长调整测试顺序。

feature-fast.png

代码覆盖率

通过添加 --coverage 标志生成代码覆盖率报告, 无需额外设置。Jest 可以从 整个项目收集代码覆盖面信息,包括未经测试的文件。

feature-coverage.png

mock简单

Jest 在测试中针对 import 使用自定义解析器, 这让模拟测试范围之外的任何对象都变得容易。 你可以将模拟的 import 和丰富的 Mock 函数 API 一起使用, 用于监视函数调用并获得可读的测试语法。

feature-mocking.png

强大调试

当测试失败时,Jest 提供了丰富的上下文帮助你找出原因。 以下是一些示例:

toBe.png

toEqual.png

toMatchSnapshot.png

开始第一条测试

安装Jest

首先用你喜欢的软件包管理工具来安装 Jest:

# 使用npm
npm install --save-dev jest
# 使用Yarn
yarn add --dev jest

准备要测试的文件

下面我们开始给一个假定的函数写测试,这个函数的功能是两数相加。首先创建 sum.js 文件:

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

编写测试文件

接下来,创建名为 sum.test.js 的文件。这个文件包含了实际测试内容:

// sum.test.js
const sum = require('./sum');

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

将如下代码添加到 package.json 中:

{
  "scripts": {
    "test": "jest"
  }
}

最后,运行 yarn test 或者 npm test ,Jest 将输出如下信息:

PASS  ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)

你刚才使用 Jest 成功地写出了第一个测试!

测试文件结构

文件命名

在上述示例中,我们将测试文件命名为 "sum.test.js",当运行jest命令时, 它会自动找到项目下的以test.js或者 spec.js 结尾的文件,这个可以在jest配置文件中通过testMatch属性进行配置, 详细介绍见后续文章,现在我们只需将测试文件名设置为 *.test.js或 *.spec.js即可。

// testMatch默认值
{
    testMatch: [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" ]
}

分组、测试、断言、匹配器

比如还针对sum函数进行测试,我们可能划分几个大的测试角度

  • 测试数字相加的情况
  • 测试字符串相加的情况
  • 测试其他特殊结构变量相加

每一个大类可以称为一组测试用例,我们通过describe来组合这一类测试。

而数字相加我们又可以拆分为以下几种情况

  • 正数相加
  • 负数相加
  • 正负相加
  • 其他情况

这是我们测试的最小单元,不能继续拆分了,我们通过test来描述(也可以用it)。

所谓测试就是看结果是否符合我们的预期,通过 expect().toBe()来进行断言,即期望某个结果等于某个值。 如果等于则该用例通过,否则用例不能通过,并可在控制台查看不通过原因。

每个test中我们可以编写多条断言,但是尽量不要太多,尽量一个test只做一个事。

const sum = require('./sum')

describe("测试数字相加", () => {
    test("正数相加", () => {
        expect(sum(1,2)).toBe(3)
    })
    test("负数相加", () => {
        expect(sum(-1,-2)).toBe(-3)
    })
    test("正负相加", () => {
        expect(sum(-1,2)).toBe(1)
    })
})

describe("测试字符串相加", () => {
    test("普通字符串相加", () => {
        expect(sum('a','b')).toBe('ab')
    })
    test("异常情况", () => {
        expect(sum('a','')).toBe('a')
        expect(sum('a',undefined)).toBe('aundefined')
        expect(sum('a',null)).toBe('anull')
    })
})

其中toBe我们可以成为匹配器,除了toBe,Jest还提供多种匹配器。

匹配器

常用匹配器

测试一个值最简单的方式就是精准相等,也就是toBe

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

toBe使用 Object.is来测试严格相等,类似于全等 ===,如果要测试对象或者数组的值是否相等, 使用toEqual,

test('object assignment', () => {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1, two: 2});
});

toEqual检查对象和数组的每个属性,不要求对象顺序必须一致

 test("toEqual不要求对象顺序", () => {
       let obj = {
           a:1,
           b:2
       }
       expect(obj).toEqual({b:2,a:1})
})

对于一个对象或者数组,toBe匹配必须确保引用地址相同才算相同,而toEqual只需要每个属性相同即可

test("toBe 和 toEqual的区别", () => {
       let obj = {
           a:1,
           b:2
       }
       expect(obj).not.toBe({a:1,b:2})  //使用not取反
       expect(obj).toBe(obj)
       expect(obj).toEqual({a:1,b:2})
})

上述用例中使用了not,代表取反

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

Truthiness匹配器

有时候需要区分undefined、null 和 false,有时候又不想区别对待, Jest有以下几种匹配器可以让你明确表示你想要什么。

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefined 指非undefined,和toBeUndefined相反
  • toBeTruthy 匹配结果和if语句中为true的情况保持一致,也就是除了这六种情况:false、0、 ''、 null、undefined和 NaN。
  • toBeFalsy 匹配结果和if语句中为false的情况保持一致
test('null', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

test('zero', () => {
  const z = 0;
  expect(z).not.toBeNull();
  expect(z).toBeDefined();
  expect(z).not.toBeUndefined();
  expect(z).not.toBeTruthy();
  expect(z).toBeFalsy();
});

toBe(true) 和 toBeTruthy的区别:

只有当expect结果严格为true时,toBe(true)才匹配通过

toBeTruthy是和if条件中为true保持一致,不严格要求为true,除了这六种情况(false、0、 ''、 null、undefined和 NaN)均匹配通过

Numbers 匹配器

  • toBeGreaterThan:匹配大于
  • toBeGreaterThanOrEqual:匹配大于等于
  • toBeLessThan:匹配小于
  • toBeLessThanOrEqual:匹配小于等于
  • toBe:匹配相等
  • toEqual:匹配相等
  • toBeCloseTo:匹配浮点数的相等
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);

  // 相等
  expect(value).toBe(4);
  expect(value).toEqual(4);
});

浮点数的相等不能用 toBe或者toEqual,想一想这是为什么

test('adding floating point numbers', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);      //该条匹配不通过      
  expect(value).toBeCloseTo(0.3); // 通过.
});

toBeCloseTo有两个参数,第一个是要比较的数字,

.toBeCloseTo(number, numDigits?)

numDigits参数限制小数点后要检查的位数,默认为2

  • numDigits为1表示 Math.abs(expected - received) < 0.05
  • numDigits为2表示 Math.abs(expected - received) < 0.005
  • 依次类推

Strings 匹配器

可以使用toMatch通过正则来匹配字符串,

test('there is no I in team', () => {
    expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
    expect('Christoph').toMatch(/stop/);
    expect('grapefruits').toMatch('fruit');
    expect('grapefruits').toBe('grapefruits');
    expect('grapefruits').toEqual('grapefruits');
});

Array 匹配器

通过toContain匹配器可以检查数组中是否包含某个值

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

    test('the shopping list has milk on it', () => {
        expect(shoppingList).toContain('milk');
        expect(new Set(shoppingList)).toContain('milk');
    });

如果是对象数组,则可以通过toContainEqual来判断数组中是否包含某个结构的对象

const users = [
        {
            name: 'li',
            age: 32
        },
        {
            name: 'wang',
            age: 33
        }
    ];

    test('the shopping list has milk on it', () => {
        expect(users).toContainEqual({name: 'li', age: 32});
    });

异常匹配器

如果要判断一个函数是否抛出异常,则可通过toThrow来匹配

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

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

  // You can also use the exact error message or a regexp
  expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
  expect(() => compileAndroidCode()).toThrow(/JDK/);
});

测试覆盖率

Jest测试覆盖率有以下几个指标

  • %Stmts: 语句覆盖率:是不是每个语句都执行了?
  • %Branch: 分支覆盖率:是不是每个if代码块都执行了?
  • %Funcs: 函数覆盖率:是不是每个函数都调用了?
  • %Lines: 行覆盖率:是不是每一行都执行了?

通过测试覆盖率可以看出我们的测试用例覆盖情况,是否达到了预期目标。

修改package.json中的script,在jest后增加 --coverage

"scripts": {
    "test": "jest --coverage"
  }

再次运行npm run jest,可以看到运行结果中包含了覆盖率相关信息。 除了四种覆盖率之外,还可以看到没有被覆盖的行号。

coverage-demo1.png

同时会看到在项目下生成了一个名为coverage的目录,里面存放多种格式的报告, 如果我们想通过html方式查看覆盖率,可以在项目下增加 jest.config.js,添加如下配置:

//jest.config.js
module.exports = {
    collectCoverage: true,
    coverageReporters: ["json","text", "lcov", "clover", "html"],
}

同时修改package中script

"scripts": {
    "test": "jest"
  }

执行npm run test后可以看到,coverage文件夹中多了一些文件,访问index.html, 可通过这种更加可视化的方式查看覆盖率详情。

覆盖率概览

coverage-html1.png

点击具体文件名称,可查看每个文件详细的覆盖情况,行上的数字代表覆盖次数,可以清晰看到那些行没有被覆盖

coverage-html2.png

好了,通过这节内容学习,基本上完成jest入门了,其实还是非常简单的,赶紧实战一下吧。

“我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!