xiaochengzi6 / Blog

个人博客
GNU Lesser General Public License v2.1
0 stars 0 forks source link

jest 使用笔记 #66

Open xiaochengzi6 opened 1 year ago

xiaochengzi6 commented 1 year ago

jest 单测

1、常见断言

2、异步测试

一、promise

检测的值返回 promise 则 jest 会等待 promise 的 resolve 状态,如果 promise 为 rejected 测试将会失败

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

二、异步函数 async/await

test('test async function', async () => {
    const data = awit fetchData()
    expect(data).toBe('peanut butter')
})

结合 .resolves or .rejects 使用

test('test async function', async () => {
    awit fetchData().resolves.toBe('peanut butter')
})

三、特别的

如果期望Promise被Reject,则需要使用 .catch 方法。

使用 expect.assertions 验证一定数量的断言被掉用,否则 fulfilled的promise 不会让测试失败

一定不要忘记把整个断言作为返回值返回

3、模拟函数

一、mock 函数

通过 jest.fn()来创建一个 mock 函数,通过检查 mock 函数的状态确保回调函数的使用

const mockFn = jest.mock() 
// or
const mockfn = jest.mock(x => x + 1)

并且所有的 mock 函数 都有 .mock属性 里面有所有的关于函数的状态

mockFn.mock.calls: 返回的是一个二元数组,数组中每项表示被调用的函数,(如果调用了三次模拟函数则数组中就有三个这样的值)每项中存的数据是参数比如:mockFn.mock.calls[0][0]: 第一个被调用的函数,选中第一个参数

mockFn = [ [arg_0, arg_1, arg_2], [arg_0, arg_1, arg_2], [arg_0, arg_1, arg_2]]

大多数情况下通常需要 mock 来创建函数就可以了不需要实际的写出真正的模拟函数

函数可以被模拟进而也可以模拟模块 通常来说导入模块一般会调用其上的方法来完成某些事物,完全可以通过 mock 去模拟这个函数,让其有返回值 通过返回值去测试代码逻辑。

二、fn

可以用 jest.fnmockImplementationOnce方法来实现Mock函数。

const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val)); // true

使用 mockImplementation去实现 mock 函数

const mockFn = jest.fn().mockImplementation(cb => cb(null, true))
myMockFn((err, val) => console.log(val)); // true

或者直接使用原始函数作为 mockFn

import {callbackFn}from './index'

jest.mock('.index')

afterEach(()=>{
  // 清除之前模拟的回调函数
  jest.clearAllMocks()
})

it('test_0', () => {
  // 前面将 index 文件下所有的导出都使用 jest.mock 包裹了一遍 所以在这里能使用
  callbackFn.mockResolvedValueOnce
})

大多数情况下函数调用都是支持链式调用的,如果想要自定义的去使用链式调用可以使用 .mockReturnThis

const myObj = {
  myMethod: jest.fn().mockReturnThis(),
};

// or

const otherObj = {
  myMethod: jest.fn(function () {
    return this;
  }),
};

使用 mockResolvedValueOnce 函数可以构建出一个 状态为 resolvepromise 实例

jest.fn.mockResolvedValueOnce()
// 等同于
jest.fn().mockImplementationOnce(() => Promise.resolve(value))

同样的还有 mockRejectedValueOnce 函数,

使用 mockImplementationOnce 实现的模拟函数功能:模拟函数的中一次调用的实现

const mockFN = jest.fn()
   .mockImplementationOnce(()=> true)
   .mockImplementationOnce(()=> false)
mockFN(1) // true
mockFN(2) // false

clearAllMocks清空所有模拟函数 useRealTimers 使用真实时间,useFakeTimers 使用模拟时间

模拟函数还有一点也很重要,模拟的函数别忘了在测试后还原回去,对于通用的模拟通常使用 beforEach 或者 beforAll 来提前指定模拟函数或者初始化一些数据,但也别忘记对应的使用 afterEach 或者 afterAll 来还原模拟函数或者清空状态

beforAll(() => {
  jest.spyOn(console, 'error').mockImplementation(()=>{})
})

afterAll(() => {
  console.log.mockRestore
})

例如:在测试中(test/it函数中)使用了 jest.useFakeTimes()那么在每一个测试结束也要去还原

afterEach(() => {
  jest.useRealTimers()
})

beforAll && afterAll 是在文件开始或结束测试时候执行的操作,beforEach && afterEach 在每一个测试开始或者结束的时候执行 这里要注意它们的调用顺序

在编写测试时通常会将相同的测试放入同一个 describe 函数中,当然直接将 test 放到全局下也没问题,在 describe 内外使用 beforAll 或者 afterAll 顺序要搞清楚,先All后Each 先外层后内层的运行,执行结束后于此相反。

mock funtion 参考

推荐文章-这里我还未看完

三、函数断言

1、toHaveBeenCalled: 来确保调用了带有特定参数的模拟函数 也可以用 .toBeCalled() 至少被调用一次(带着特定的参数)

// 判断参数
function dringk(callback, flavour){
    if(flavour !== 'b'){
        callback(flavour)
    }
}
// 测试
describe('test function arguments', () => {
    test('arguments no b', () => {
        const mockFn =  jest.fn()
        dringk(mockFn, 'b')
        // 确保没有被调用
        expect(drink).not.toHaveBeenCalled()
    })
})

2、toHanveBeenCalledTimes(number) :确保调用模拟函数的次数是准确的 也可以用toBeCalledTimes(number)

3、`toHaveBeenCalledWith(arg1, arg2, ...) : 确保调用了带有特定参数的模拟函数 也可以用 toBeCalledWith()

4、toReturn|| toHaveReturn给模拟函数提供返回值

5、.toReturnTimes(number)|| `.toHaveReturnedTimes(number) 函数返回值的次数 抛错不会统计在内

断言参考

4、关于前端测试

  1. 关于测试你需要了解在前端种有那些测试前端测试一共有哪几种?

  2. 测试也需要明确用户,最终用户只考虑页面的交互,而开发者只关心 props 的问题,测试就是要满足这两位开发者的要求才行这里推荐一篇文章介绍了前端单测为什么不需要测试细节

  3. 前端测试应该测试什么? "如果你只考虑代码本身,很容易、也很自然地走向测试代码细节的不归路。我们应该要考虑那些更接近用户的真实使用场景来写测试。",代码是最终为了实现某种功能而写的,那么只需要考虑用户的使用用例有那些?通过它来测试代码。这里的一篇文章说的很不错前端单测应该测试什么?

  4. 很有必要去仔细阅读这里介绍了很多市面上常见出现的问题以及版本更新带来的差异性问题使用 React Testing Library 的 15 个常见错误

  5. 在测试代码的时候总有一些数据重复或冗余的情况,写一些函数或者类提前对其进行封装导致测试用例很难去阅读这里的一篇文章去介绍了如何维护测试代码怎么给测试代码做抽象才是有意义的? 这里我没看懂多少,在 测试上使用的次数不是很多没有太多的感悟

  6. "尝试将您的测试彼此隔离,并将重点放在用例而不是功能上"这里的一篇文章论证了这个观点并指出了如何写好一个测试Test Isolation with React

  7. 关于如何测试react-hook 这里有篇文章如果没有了解过如何测试 hook 可以去学习看看,但我认同他观点

    react 中并不能直接去测试 hook 所以如果需要测试需要在 ui 组件中去测试,如果在组件中去测试那你就要为这个hook 编写一个使用场景,而 hook 的场景可能是多种多样的 每一个参数与方法都可以被细化为一个场景,通过场景去测试 hook 是不准确的也是极其麻烦的事情。

    我并不认同它书写测试代码的风格,我看过 ahook 中大部分的源码,它的测试代码非常的清爽,它主要是面向功能的测试,判断默认值、每一个函数的调用情况、每一个分支,它会将所有可能性全部测试一遍。这和之前了解到的按照使用案例的方式去编写测试代码不同,这是因为 hook 是为开发者使用的而不是最终用户,每一个函数或者参数都有可能觉得 hook 最终的状态 且 作为一个通用且重复的 hook 测试每一个分支和方法是非常必要的

  8. 杂谈: 在什么场景下使用 TDD 测试驱动开发 推荐这篇文章

  9. 杂谈:测试出现的误区前端测试常见的 3 个误区

  10. [如何测试Http请求](测试中如何处理 Http 请求?) && 为什么不使用Shallow Rendering这样文章还未来得及看,等用到在看,痛点驱动式学习!!!