加载中...
  • Jest 简单介绍与使用

    说明:我们的团队用的umi开发,所以下面会带点umi的代码,因为Umi框架内置了jest库 所以我们无需进行安装

    一 jest 常见断言 (测试匹配器)

    toBe

    toBe 匹配器有种类似于object.is或者===,精确相等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    test('测试toBe', () => {
    // toBe为匹配器 matchers
    // toBe相当于js中的 object.is ===
    expect(10).toBe(10); // passed
    });


    test('测试toBe', () => {
    const a = {one: 1}
    expect(a).toBe( {one: 1}); // failed,因为两个对象的地址是不一样的
    });

    toEqual

    测试对象的内容是否相等,不比较对象的地址,只关心对象的内容是否一致,递归检查对象或数组的每个字段。

    1
    2
    3
    4
    test('测试toEqual', () => {
    const a = {one: 1}
    expect(a).toEqual( {one: 1}); // passed
    });

    toBeNull

    测试某个变量是否为null,如果是则Passed,否则failed

    1
    2
    3
    4
    test('测试toBeNull', () => {
    const a = null
    expect(a).toBeNull(); // passed
    });

    toBeUndefined 和 toBeDefined

    测试某个变量是否未定义,如果是则Passed,否则failed

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    test('测试toBeUndefined', () => {
    const a = undefined;
    expect(a).toBeUndefined(); // passed
    });

    test('测试toBeUndefined', () => {
    const a = '';
    expect(a).toBeUndefined(); // failed
    });

    test('测试toBeUndefined', () => {
    const a = null;
    expect(a).toBeUndefined(); // failed
    });


    test('测试toBeDefined', () => {
    const a = null;
    expect(a).toBeDefined(); // passed
    });

    test('测试toBeDefined', () => {
    const a = undefined;
    expect(a).toBeDefined(); // failed
    });

    toBeTruthy

    测试某个变量是否为真,如果是则Passed,否则failed

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    test('测试toBeTruthy', () => {
    const a = undefined;
    expect(a).toBeTruthy(); // undefined 视为false
    });

    test('测试toBeTruthy', () => {
    const a = null;
    expect(a).toBeTruthy(); // null视为false
    });

    test('测试toBeTruthy', () => {
    const a = 0;
    expect(a).toBeTruthy(); // 0 视为false
    });

    test('测试toBeTruthy', () => {
    const a = 1;
    expect(a).toBeTruthy(); // 1 视为true
    });

    toBeFalsy

    测试某个变量是否为假,如果是则Passed,否则failed

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    test('测试toBeFalsy', () => {
    const a = 1;
    expect(a).toBeFalsy(); // failed,因为1 视为true
    });

    test('测试toBeFalsy', () => {
    const a = undefined;
    expect(a).toBeFalsy(); // passed,因为undefined 视为false
    });

    test('测试toBeFalsy', () => {
    const a = null;
    expect(a).toBeFalsy(); // passed,因为null 视为false
    });

    test('测试toBeFalsy', () => {
    const a = 0;
    expect(a).toBeFalsy(); // passed,因为0 视为false
    });

    test('测试toBeFalsy', () => {
    const a = 0;
    expect(a).not.toBeFalsy(); // failed,因为0 视为false,但是匹配器要的是真
    });

    数字相关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    test('测试toBeGreaterThan', () => {
    const count = 10;
    expect(count).toBeGreaterThan(9); // passed,表示希望count这个变量的值比9大
    });

    test('测试toBeLessThan', () => {
    const count = 10;
    expect(count).toBeLessThan(9); // failed,表示希望count这个变量的值比9小
    });

    test('测试toBeGreaterThanOrEqual', () => {
    const count = 9;
    expect(count).toBeGreaterThanOrEqual(9); // passed,表示希望count这个变量的值大于等于9
    });

    test('测试toBeLessThanOrEqual', () => {
    const count = 9;
    expect(count).toBeLessThanOrEqual(9); // passed,表示希望count这个变量的值小于等于9
    });


    test('测试toBeCloseTo', () => {
    const firstNumber = 0.1;
    const secondNumber = 0.2;
    expect(firstNumber + secondNumber).toEqual(0.3); // 结果是failed,因为js计算浮点数的时
    expect(value).toBe(0.3); // 这句会报错,因为浮点数有舍入误差候,有可能会溢出或者说不准确,这种情况下最好用toBeCloseTo
    });

    test('测试toBeCloseTo', () => {
    const firstNumber = 0.3;
    const secondNumber = 0.4;
    expect(firstNumber + secondNumber).toBeCloseTo(0.7); // passed
    });

    字符串相关

    1
    2
    3
    4
    5
    test('测试toMatch', () => {
    const str = 'www.baidu.com';
    expect(str).toMatch('baidu'); // passed, 表示str字符串中是否包含baidu这个字符串,是返回passed
    expect(str).toMatch(/baidu/); //passed,这里还可以写正则表达式
    });

    数组相关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    test('测试toContain', () => {
    const arr = ['dee', 'lee'];
    expect(arr).toContain('dee'); // passed, 表示arr数组中是否包含dee这个字符串元素,是返回passed
    });

    test('测试toContain', () => {
    const arr = ['dee', 'lee'];
    const data = new Set(arr);
    expect(data).toContain('dee'); // passed, 表示arr数组中是否包含dee这个字符串元素,是返回passed
    });

    异常情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const throwNewErrorFunc =  () => {
    throw new Error('this is a new error');
    }

    test('测试toThrow', () => {
    expect(throwNewErrorFunc).toThrow(); // passed, 表示希望throwNewErrorFunc这个方法运行的时候能够抛出一个异常
    });

    test('测试toThrow', () => {
    expect(throwNewErrorFunc).not.toThrow(); // failed, 表示希望throwNewErrorFunc这个方法运行的时候不能够抛出异常
    });

    test('测试toThrow', () => {
    expect(throwNewErrorFunc).toThrow('this is a new error'); // passed, 表示希望throwNewErrorFunc这个方法运行的时候能够抛出一个异常,并且内容是'this is a new error'
    expect(throwNewErrorFunc).toThrow(/this is a new error/); // 也可以是正则表达式
    });

    二 测试案例

    针对方法的测试:

    1.测试方法的两个案例

    1.1

    src目录下新建一个common/index.js开始添加一个简单的方法 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function add(a,b){
    return a+b;
    }
    function minnus(a,b) {
    return a-b;
    }
    module.exports={
    add,
    minnus
    }

    在跟目录新建目录 test/common/index.test.js 用于自动测试index.js 中的方法 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { add , minnus } from '../../src/common/index'

    // 尽量每次编写测试用例都用describe包裹进行分块
    // 每个测试用例一个it函数代表
    // 参数:
    // 字符串,代表测试用例名称:常用命名模式“被测对象在什么情况下是什么行为”
    // 函数,实际测试用例过程
    describe('测试common/index 文件相关代码', () => {
    // 测试用例
    it('调用 add方法执行 1+1=2',()=>{
    // 测试调用后的预期值为2
    expect(add(1,1)).toBe(2)
    })
    it('调用 minnus方法 执行1-1=0',()=>{
    // 测试调用后的预期值为0
    expect(minnus(1,1)).toBe(0)
    })
    })

    1.2

    开发代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default class Counter{
    constructor(){
    this.number = 0 ;
    }
    addOne(){
    this.number += 1;
    }
    minusOne(){
    this.number -= 1;
    }
    }

    test测试文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    import Counter from '../../src/common/index'
    const counter = new Counter();

    describe('测试common/index 文件相关代码', () => {
    it('测试counter中addOne方法',()=>{
    counter.addOne(); // 调用addOne方法后 number 执行了 +1 正确结果为1
    expect(counter.number).toBe(1) // 表示number值的正确结果为1
    })
    it('测试counter中minusOne方法',()=>{
    counter.minusOne(); // 调用addOne方法后 number 执行了 - 1, 而上面执行了addOne()方法 正确结果为 0
    expect(counter.number).toBe(0) // 表示number值的正确结果为 0
    })
    })

    [图片上传失败…(image-e7c678-1622985585990)]

    2.测试异步代码

    这是非常常见的通用处理方式,比如你有一个fetchData(callback)的function用来获取数据,并且在获取完成的时候调用callback 函数,你想测试返回的数据是“peanut butter” ,默认情况下当fetchData执行完成的时候Jest的测试就完成了,这并不是你所期望的那样的去运行。

    方法一:回调函数

    代码示例:fetechData.js文件代码

    1
    2
    3
    4
    5
    6
    import request from "umi-request";
    export const fetchData = (fn:any)=>{
    request.get("http://mock-api.com/RKDx59Ka.mock/test").then((response)=> {
    fn(response.data)
    })
    }

    对应的测试用例fetechData.test.js文件代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import  { fetchData}  from '../../src/common/fetechData'

    test('测试 fetchData返回结果为{list: [],state: false}',(done)=>{
      fetchData((data)=>{
    // 对比返回的 data数据是否一致
      expect(data).toEqual({
      list: [],
    state: false
        })
        done(); // 回调函数,测试用例执行完成执行done 
      })
    })

    方法二:使用Promise方法

    代码示例:fetechData.js文件代码

    1
    2
    3
    4
    import request from "umi-request";
    export const fetchData = ()=>{
    return request.get("http://mock-api.com/RKDx59Ka.mock/test")
    }

    对应的测试用例fetechData.test.js文件代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import  { fetchData}  from '../../src/common/fetechData'

    test('测试 fetchData返回结果为{list: [],state: false}',()=>{
      return fetchData().then((response)=>{
        expect(response.data).toEqual({
          list: [],state: false
        })
      })
    })

    如果要测试404效果可以这样 故意把接口地址改成没用的地址
    对应的测试用例fetechData.test.js文件代码

    1
    2
    3
    4
    5
    6
    test('测试 fetchData返回结果 404',()=>{
    return fetchData().catch(e=>{
    console.log('e',e);
    expect(e.toString().indexOf('404')>-1).toBe(true)
    })
    })

    方法三:使用 Async/Await

    我相信大家对Async/Await 是比较熟悉的,你可以在测试中使用异步和等待。要编写一个async测试,只需在传递到测试的函数前面使用async关键字。例如上面同样的fetchData场景可以使用下面的实现:

    对应的测试用例fetechData.test.js文件代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import  { fetchData}  from '../../src/common/fetechData'

    test('测试 fetchData返回结果为{ code: 0, data: { list: [], state: false } }',async ()=>{
    let obj;
    await fetchData().then((res)=>{
    obj = res;
    })
    const obj2 = { code: 0, data: { list: [], state: false } }
    // tomatchobject检查一个JavaScript对象是否匹配一个对象的属性子集
    expect(obj).toMatchObject(obj2);
    })

    3.Jest中钩子函数

    在jest中,如果测试用例中需要使用到某个对象 或 在执行测试代码的某个时刻需要做一些必要的处理,直接在测试文件中写基础代码是不推荐的,可以使用jest的钩子函数。

    钩子函数概念:在代码执行的某个时刻,会自动运行的一个函数。

    demo2 上写的 的测试用例可以正常执行,但是addOne()函数调用的次数会影响counter.number的值,就会影响到minusOne方法执行结果。如果你想addOne与minusOne方法调用互不影响时,此时就不得不引入jest的钩子函数。

    beforeAll

    在所有测试用例执行之前执行一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    import Counter from '../../src/common/index'
    let counter = null;

    beforeAll(()=>{
    console.log('beforeAll')
    counter = new Counter();
    })
    test('测试counter中addOne方法',()=>{
    counter.addOne();
    expect(counter.number).toBe(1)
    })
    test('测试counter中minusOne方法',()=>{
    counter.minusOne();
    expect(counter.number).toBe(0)
    })

    注意:使用beforeAll时,需要把之前const类型修改成let 。此时还是会相互影响,依旧不能达成目标。

    beforeEach

    在每个测试用例执行之前执行一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    import Counter from '../../src/common/index'
    let counter = null;

    beforeAll(()=>{
    console.log('beforeAll')
    })
    beforeEach(()=>{
    console.log('beforeEach')
    counter = new Counter();
    console.log('counter.number',counter.number)
    })
    test('测试counter中addOne方法',()=>{
    counter.addOne();
    expect(counter.number).toBe(1)
    })
    test('测试counter中minusOne方法',()=>{
    counter.minusOne();
    expect(counter.number).toBe(0)
    })

    此时直接执行npm run test命令时会有一个用例执行失败,执行结果如图所示:

    RPXGRS.jpg

    此时返回的结果已经不是0了 ,而是-1!此时addOne 与minusOne两个测试用例已经不会相互影响了。每次执行测试用例前,counter都会初始化!

    afterAll

    在所有测试用例执行完之后执行一次

    afterEach

    在每个测试用例执行完成之后执行一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    import Counter from '../../src/common/index'
    let counter = null;

    // 在所有测试用例执行之前执行一次
    beforeAll(()=>{
    console.log('beforeAll')
    counter = new Counter();
    console.log('counter.number',counter.number)
    })

    // 在每个测试用例执行之前执行一次
    beforeEach(()=>{
    console.log('beforeEach')
    counter = new Counter();
    })

    // 在每个测试用例执行完成之后执行一次
    afterEach(()=>{
    console.log('afterEach')
    })

    //在所有测试用例执行完之后执行一次
    afterAll(()=>{
    console.log('afterAll')
    })

    test('测试counter中addOne方法',()=>{
    counter.addOne();
    expect(counter.number).toBe(1)
    })
    test('测试counter中minusOne方法',()=>{
    counter.minusOne();
    expect(counter.number).toBe(-1)
    })

    钩子函数执行顺序:

    根据上边案例实际打印结果可以看出这四个钩子函数的执行顺序,如下:

    (1)beforeAll > (2)beforeEach > (3)afterEach > (4)afterAll

    靠前的一次优先执行。

    4.snapshot快照测试

    1、快照测试

    项目中经常有一些配置文件。比如

    1
    export const generateConfig = ()=>{ return { server :'http://localhost', port:'8080', domain:'localhost' } }

    对应它的测试用例可以这样写 snapshot.test.js 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import  { generateConfig}  from '../../src/common/snapshot'

    test('测试 generateConfig', () => {
    expect(generateConfig()).toEqual({
    server: 'http://localhost',
    port: '8080',
    domain:'localhost'
    })
    })

    当配置项不断增加的时候,就需要不断去更改测试用例。

    那么我们可以使用快照写测试用例:

    1
    2
    3
    4
    5
    import  { generateConfig}  from '../../src/common/snapshot'

    test('测试 generateConfig', () => {
    expect(generateConfig()).toMatchSnapshot()
    })

    运行测试用例之后会自动生成快照文件,对应目录如下:  

    RPXtMQ.jpg

    toMatchSnapshot() 会为expect 的结果做一个快照并与前面的快照做匹配。(如果前面没有快照那就保存当前生成的快照即可)

    这在配置文件的测试的时候是很有用的,因为配置文件,一般不需要变化。

    当然,确实要改配置文件,然后要更新快照,也可。

    2、配置文件修改

    如果配置文件发生变化,运行时会提示已经发生变化,可以根据提示的命令确定修改文件:‘npm test – -u’

    RPXJxg.jpg

    如果添加new Date()这样的参数,配置文件在实时的更新,如何处理呢?对应代码如下:
    snapshot.js

    1
    2
    3
    4
    5
    6
    7
    8
    export const generateConfig  = () => {
    return {
    server: 'http://localhost',
    port: '8080',
    domain:'localhost',
    time:new Date()
    }
    }

    测试用例snapshot.test.js

    1
    2
    3
    4
    5
    6
    7
    import  { generateConfig}  from '../../src/common/snapshot'

    test('测试 generateConfig', () => {
    expect(generateConfig()).toMatchSnapshot({
    time :expect.any(Date)// Date String Number等等
    })
    })

    3、行内快照

    先需要安装 prettier ,否则行内快照无法执行。

    安装命令:npm install prettier

    安装完成之后,修改测试用例:

    1
    2
    3
    4
    5
    6
    7
    import  { generateConfig}  from '../../src/common/snapshot'

    test('测试 generateConfig', () => {
    expect(generateConfig()).toMatchInlineSnapshot({
    time :expect.any(Date)// Date String Number等等
    })
    })

    运行测试用例之后,会多出一个参数来,表示不匹配 结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { generateConfig } from '../../src/common/snapshot';

    test('测试 generateConfig', () => {
    expect(generateConfig()).toMatchInlineSnapshot(
    {
    time: expect.any(Date), // Date String Number等等
    },
    `
    Object {
    "domain": "localhost",
    "port": "8080",
    "server": "http://localhost",
    "time": Any<Date>,
    }
    `,
    );
    });
    上一篇:
    Jest 简单介绍与使用
    下一篇:
    看到好的东西想与你分享
    本文目录
    本文目录