入门前端自动化测试-jest-基础

Posted lin-fighting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了入门前端自动化测试-jest-基础相关的知识,希望对你有一定的参考价值。

jest基础

匹配器

jest默认环境是node

如果想在jest环境使用esmodule,需要借助@babel/core转化工具, @babel/preset-env指明如何转化


    "presets": [
        [
            "@babel/preset-env",
            
                "targets": 
                    "node": "current"
                
            
        ]
    ]

只需要一个项目中安装jest,然后执行 npx jest --watch,jest就会自动将所有的.test.js结尾的文件中的测试用例执行。

jest提供了很多匹配器,最基本的使用

test('测试加法', ()=>
     // toBe 类似 Object.is(a,b),适用于普通值
    expect(10).toBe(10)
 )


 test('测试内容', ()=>
   // 匹配对象使用toEqual,递归遍历使用toBe, 加上.not就是取反
   expect(a: 1).toEqual(a: 1)
 )

如toBe, toEqual,等等还有很多。当执行npx jest --watch的时候,jest就会执行这些测试用例并返回结果。

PASS  ./9.test.js
  √ 测试加法 (2ms)测试内容 (1ms)

Snapshot Summary
 › 1 snapshot file obsolete from 1 test suite. To remove it, press `u`.
   ↳   • __snapshots__学习\\demo.test.js.snap

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   1 file obsolete, 0 total
Time:        0.987s
Ran all test suites related to changed files.

测试通过。

还有更多的适配器如:

 expect(10).toBeGreaterThan(9) //大于9
 expect( 0.1 + 0.2).toBeCloseTo(0.3) //解决精度问题
 expect('abcd').toMatch(/ab/) //匹配字符串
 expect([1,2]).toContaine(1) //数组或者集合是否包含某个数
 expect(()=>throw new Error('123')).toThrow() //抛出异常, 没有toThrow不会抛出异常
 expect(()=>throw new Error('123')).toThrow('123') //是否抛出123异常,是的话测试通过

更多适配器可以查看官网。

命令行工具

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 file obsolete, 0 total
Time:        2.302s, estimated 3s
Ran all test suites related to changed files.

Watch Usage: Press w to show more.

执行完后,下面会提示输入W显示更多指令

Watch Usage
 › Press a to run all tests.
 › Press f to run only failed tests.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press u to update failing snapshots.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.
  • a执行所有测试 默认 jest --watchAll 就是打开了a模式

  • f只测试失败的测试

  • p 根据文件名字正则表达式,匹配更改的test

  • t 根据文件名字,过滤某些test

  • u 更改失败的快照(后面会讲)

  • o 配合git,只执行有修改的文件 默认–watch就是打开了。

测试异步

// 默契请求的方法
const fetchData = () =>
  new Promise((resolve, reject) => 
    setTimeout(() => 
        resolve( success: true );
    , 2000);
  );

export  fetchData ;

因为默认情况下,Jest 测试在执行结束后完成。

test('the data is peanut butter', () => 
  fetchData().then(res=>
      expect(res).toEqual(success: true)
  )
);

那么其实不管输入什么,该test就会执行正确。

正确测试异步方法:

1 done

test("fetchData 返回结果为success: true", (done) => 
  fetchData()
    .then((res) => 
      expect(res).toEqual( success: true );
      done();
    )
    .catch((err) => 
      done(err);
    );
);

只有调用done,才会正确测试该test。

2 promise

正常使用promise.then,但是一定要return。而且可以使用expect.assertions(1)表示至少执行一次expect。

因为有些场景,我们只俘获了catch,并无俘获then,那么Promsie成功的时候,这个测试将会没有执行。所以需要这个来确定至少执行一次。否则失败。

test("fetchData 返回结果为success: true", () => 
  expect.assertions(1) //至少执行一次expect,如果没跑也会报错
  return fetchData()
    .then((res) => 
      expect(res).toEqual( success: true );
    )
    .catch((err) => 
      expect(err).toMatch("error");
    );
);

3 resolves/reject

// 可以.resolves在您的期望语句中使用匹配器,Jest 将等待该承诺解决。如果 promise 被拒绝,测试将自动失败。
test("fetchData 返回结果为success: true", () => 
  return expect(fetchData()).resolves.toMatchObject(
    success: true,
  );
  // 期望一个 Promise 被拒绝,使用.rejects匹配器。如果promise被拒绝,就执行,如果promise成功执行,则该jest默认失败
  return expect(fetchData()).rejects.toThrow()
);	

4 asycn/await

记得错误处理

test("fetchData 返回结果为success: true", async () => 
  try 
    const data = await fetchData();
    expect(data).toEqual(
      success: true,
    );
   catch (err) 
    // 抛出异常
    expect(err).toThrow()
  
);

钩子函数

jest在执行test用例的时候,提供了很多钩子,用来初始化一些东西或者做一些销毁工作,如执行前调用,执行后调用等等。如

beforeAll(()=>
    console.log('beforeAll');
)

afterAll(()=>
    console.log('afterAll');
)

他们分别会在所有的钩子执行前执行和执行后执行。

beforeEach(()=>
    console.log('beforeEach');
)

afterEach(()=>
    console.log('afterEach');
)

beforeEach和afterEach分别会在每个test执行之前和之后运行。

分组

相同的意义的测试用例可以写在同一个分组中,相当于ts的namespace,jest提供了describe

describe("测试加法", () => 
  beforeAll(() => 
    console.log("内部 eforeAll");
  );
  afterAll(() => 
    console.log("内部 afterAll");
  );
  afterEach(() => 
    console.log("内部 afterEach");
  );
  beforeEach(() => 
    console.log("测试加法 beforeEach");
    counter = new Counter();
  );

  test("test Counter addOne", () => 
    counter.addOne();
    expect(counter.number).toBe(1);
    console.log("addOne");
  );
);

如,describe内部的钩子,只会在内部执行。而整个test文件,可以看作是外层包装了一个descrbie(‘xx’,()=>// test文件的内容)

那么desceibe内部的钩子和外部的钩子,执行顺序是怎样呢?

以下面的为例子

beforeAll(() => 
    console.log("外部 beforeAll");
  );
  
  afterAll(() => 
    console.log("afterAll");
  );
  beforeEach(() => 
    console.log("外部 beforeEach");
  );
  afterEach(() => 
    console.log("外部 afterEach");
  );
  describe("测试加法", () => 
    beforeAll(() => 
      console.log("内部 eforeAll");
    );
    afterAll(() => 
      console.log("内部 afterAll");
    );
    afterEach(() => 
      console.log("内部 afterEach");
    );
    beforeEach(() => 
      console.log("测试加法 beforeEach");
    );
  
    test("test Counter addOne", () => 
      console.log("descrbie内部的测试 addOne");
    );
  );

执行顺序就是

外部 beforeAll
内部 beforeAll
外部 beforeEach
测试加法 beforeEach
descrbie内部的测试 addOne
内部 afterEach
外部 afterEach
内部 afterAll
外部 afterAll

技巧就是:外部beforeAl > 内部beforeAll > 外部beforeEach > 内部beforeEach > test执行 > 内部afterEach > 外部 afterEach > 内部 afterAll > 外部 afterAll

mock

jest提供了mock的功能,可以模拟请求数据的函数,模拟返回的数据。

其中

jest.fn 返回一个mock函数,1 捕获函数的调用和返回结果, 2 可以自由地设置返回结果 3模拟axios请求返回特定数据
test("测试", () => 
  const func = jest.fn((a) => 
    return a + 4;
  );

  // func.xxx有很多语法。
  // func.mockImplementtation((a)=>return a+4)跟jest.fn(fn)的fn一样,函数执行的时候的逻辑
  // func.mockImplementationOnce(()=>) 只执行一次。

  // mockReturnValue('a') 每次调用都返回a
  func.mockReturnValueOnce("11111").mockReturnValueOnce("2222"); //第1,2次执行func的时候,返回了1111, 2222
  func(1);
  func(2);
  func(3);
  func(4);

  // func是否被执行过
  //expect(func).toBeCalled()
  //expect(func.mock.calls[0]).toEqual([1])
  expect(func.mock.results);
  console.log(func.mock);

  /** 
     console.log(func.mock);
     可以通过func.mock.calls.length判断调用多少次,以及传参, fn.results判断返回值
      calls: [ [ 1 ], [ 2 ], [ 3 ], [ 4 ] ],
      instances: [ undefined, undefined, undefined, undefined ], func运行的时候,this指向
      invocationCallOrder: [ 1, 2, 3, 4 ],
      results: [
         type: 'return', value: '11111' ,
         type: 'return', value: '2222' ,
         type: 'return', value: 7 ,
         type: 'return', value: 8 
      ]
    

     
);

func执行的时候,实际上是jest.fn()在执行,func.mock提供了很多内部,包括调用多少次,每次的入参,每次的返回等等。并且可以通过func.mockReturnValueOnce(‘xx’)默契一次func调用的时候的返回。

jest.mock可以改变函数的实现
// 3 改变函数的内部实现
import axios from 'axios'
jest.mock('axios')
test.only('测试 axios',async()=>
    axios.get.mockResolvedValue(data: '123') //模式请求返回数据
    console.log('axios', axios);
    await axios.get('xxx').then(data=>
        console.log(data);
        expect(data).toEqual(data: '123') //断言
    )
)
jest.mock(‘axios’)可以将axios上的所有属性变成jest.fn。打印出来的axios是
 console.log 9.test.js:5
    axios <ref *1> [Function: wrap] 
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      ....
    

axios已经被改造了。可以通过axios.get.mockResolvedValue(data: ‘123’)模式get请求返回的数据,测试如下:

 PASS  ./9.test.js
  √ 测试 axios (10ms)

  console.log 9.test.js:6
     data: '123' 

Snapshot Summary
 › 1 snapshot file obsolete from 1 test suite. To remove it, press `u`.
   ↳   • __snapshots__学习\\demo.test.js.snap

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 file obsolete, 0 total
Time:        2.568s, estimated 3s
Ran all test suites related to changed files.

测试通过,get请求返回的data就是data: ‘123’

自己改造函数

jest执行测试用例的时候,并不希望真正的发送请求。所以可以通过jest.mock模拟。

假设fetchData是代码中真正发送请求的函数,我们想验证这个函数的正确性,又不想发送请求。可以在根目录下创建__mock__文件夹,命名相同的文件名字,如demo.js然后实现

// __mock__/demo.js
const fetchData = () =>
  new Promise((resolve, reject) => 
    setTimeout(() => 
        resolve( success: true );
    , 2000);
  );

export fetchData  

接着引入fetchData的时候

import fetchData from './demo'
jest.mock('./demo') //该语句的用途是去__mock__下面找demo文件,然后看有没有导出的方法,这样上一条语句导入的fetchData就变成了从__mock__/demo.js中导入的我们自己模拟的fetchData,然后去执行。
// 有多个引入的时候,可以通过const aa = jest.requireActual('./demo'),这样,aa就回去真正的demo文件下引入了。
test('xxx',()=>
    return fetchData().then(res=>.....)
)

其次,如果jest.mock(’./demo’)找不到__mock__下的文件的时候,jest内部就会自动模拟,如上面的axios。

mock模拟类
// util.js
class Util 
    a()
        setTimeout(()=>
            console.log(123);
        , 3000)
    
    b()
        setTimeout(()=>
            console.log(123);
        , 5000)
    

export default  Util

// test.js
import Util from './util'
jest.mock('./util') //因为我们没有在__mock__下定义该文件, jest.mock发现util是一个类,就自动把类的构造函数和方法变成Jest.fn()
// const Util = jest.fn()
// Util.prototype.a = jest.fn()
// Util.prototype.b = jest.fn()

我们也可以自己定义

// __mock__/util.js
 const Util = jest.fn()
 Util.prototype.a = jest.fn()
 Util.prototype.b = jest.fn()

这样jest.mock(’./util’)就会来这里找。

甚至可以

// test.js
jest.mock('./util', ()=>
 const<

以上是关于入门前端自动化测试-jest-基础的主要内容,如果未能解决你的问题,请参考以下文章

前端自动化测试框架Jest中的Mock

前端测试框架Jest总结

前端自动化测试框架 Jest 极简教程

Jest 单元测试入门

前端开发工具集:单元测试(jest)

前端angular基础测试篇