低代码: 组件库测试的必要性以及Jest测试工具中的异步,mock工具

测试的重要性


1 )出现的问题

  • 新建一堆不同属性的组件,肉眼观测
  • 更新代码,要再次进行手动测试

2 )测试在国内被严重忽视

  • 没有时间
  • 需求一直修改
  • 不知道怎样写测试

3 )测试的优点

  • 自动化完成流程,保证代码的运行结果
  • 更早发现 Bug
  • 重构和升级更加容易和可靠

4 )测试金字塔

4.1 Martin Fowler 在2012年提出

  • 底座:Unit Test, 单元测试,没有相互依赖的,独立的各个功能
  • 中间:Service Test, 用于组合Unit Test
  • 顶层:UI Test 或者 E2E Test,模拟真实用户场景,填写表单,模拟点击等操作
  • 以上看出,单元测试 占比非常多,非常容易编写和修改维护,效率高

4.2 国内测试情况,倒金字塔型

  • 顶部是手动测试,占比非常大
  • 之后是UI测试,QA写一系列测试驱动程序,模拟用户操作来测试
  • 之后是服务测试,和单元测试,属于开发人员范畴,几乎不写

鉴于国内情况特殊,我们并不推荐,对于一个完善的组件库产品,我们倾向于对其进行编写单元测试

组件化的框架(React,Vue)非常适合写单元测试

  • 组件化,独立单元,互不影响
  • 属性和界面的映射,固定输入,固定输出
  • 单向数据流,只需要测试是否触发相应的回调和发送对应事件
  • 状态管理工具的 store 可以单独写测试,数据的变化都在store中

通用测试框架

测试框架的几大功能

  • 断言
    • Jest 内置
    • Mocha 需要另外安装 Chai 或者其他断言库
    • 异步支持: Jest 和 Mocha 支持良好
  • Mock
    • Jest 内置
    • Mocha 需要另外安装 Sinon
  • 代码覆盖率
    • Jest 内置
    • Mocha 需要另外安装 Istanbul

1 )Jest的特点

  • 开箱即用,零配置
  • 快!
  • 内置代码覆盖率
  • Mocking 很容易

2 )Mocha的特点

  • 灵活
  • 可扩展

Jest

  • 查看 Jest 版本: $ npx jest --version
  • 测试文件命名 xx.test.js
  • 断言:using-matchers
    • toBeNull matches only null
    • toBeUndefined matches only undefined
    • toBeDefined is the opposite of toBeUndefined
    • toBeTruthy matches anything that an if statement treats as true
    • toBeFalsy matches anything that an if statement treats as false
  • 执行测试文件
    • $ npx jest 执行一次
    • $ npx jest async.test --watch 监视改动,变化后执行

1 ) 异步支持功能

async.test.js

// 场景1 callback done
const fetchUser = () => {
	setTimeout(() => {
		cb('hello');
	}, 100)
}

// 测试异步
it('test callback', (done) => {
	fetchUser((data) => {
		expect(data).toBe('123')
	})
})

// 场景2 promise
const userPromise = () => Promise.resolve('hello')

// 测试promise
it('test Promise', () => {
	// 这里 return 要加上
	return userPromise().then(data => {
		expect(data).toBe('hello')
	})
})

// 场景3 async/await
it('test async await', async () => {
	const data = await userPromise()
	expect(data).toBe('hello')
})

// 场景4 expect
it('test expect', async () => {
	return expect(userPromise()).resolves.toBe('hello')
})

// 场景5 综合应用 
const rejectPromise = () => Promise.reject('error')
it('test with expect reject', async () => {
	return expect(rejectPromise()).rejects.toBe('error')
})

以上场景1 不会测试通过,场景2, 3, 4, 5可通过

2 ) Mock 功能

  • 为什么要有 Mock?
    • 前端需要有网络请求
    • 后端依赖数据库等模块
    • 局限性:依赖其他的模块
  • Mock 解决方案
    • 测试替代,将真实代码替换为替代代码
  • Mock 的两大功能
    • 创建 mock function,在测试中使用,用来测试回调
    • 手动 mock,覆盖第三方实现

2.1 jest.fn 的用法

function mockTest(shouldCall, cb) {
	if (shouldCall) {
		return cb(42)
	}
}

it('test with mock function', () => {
	const mockCb = jest.fn() // 这个 jest.fn 假的函数实现,类似监听器,收集函数调用信息
	mockTest(true, mockCb)
	expect(mockCb).toHaveBeenCalled() // 验证是否已经被调用
	expect(mockCb).toHaveBeenCalledWith(42) // 验证调用时携带的参数是 42
	expect(mockCb).toHaveBeenCalledTimes(1) // 验证调用了多少次
	// console.log(mockCb.mock.calls)
	// console.log(mockCb.mock.results)
})

it('test mock with implementation', () => {
	// const mockCb = jest.fn(x => x * 2)
	const mockCb = jest.fn().mockReturnValue(20)
	mockTest(true, mockCb)
	console.log(mockCb.mock.results) // [ { type: 'return', value: 20 } ]
})
  • jest.fn() 可以用于验证事件,比如是否调用了函数,调用函数的参数是什么
  • 监听过程是不会被 mockTest 感知到

2.2 手动mock

新建 user.js, 用于测试手动 mock

const axios = require('axios')

module.exports = function getUserName(id) {
	const url = `https://xx.com/x/api/user/${id}`
	return axios.get(url).then((resp) => {
		return resp.data.username // 假设id为1,这里的username 是 wang
	})
}

现在我们来测试 user.js 模块, 参考 API

编写 mock.test.js

先看下 mockImplementation 这个API

const getUserName = require('./user')
const axios = reuqire('axios')

jest.mock('axios')
// 这里替换对应的实现,修改结果
axios.get.mockImplementation(() => {
	return Promise.resolve({ data: {username: 'Lee'} })
})

// 测试某个api模块
it('test with mock modules', () => {
	return getUserName(1).then(name => {
		console.log(name);
		expect(axios.get).toHaveBeenCalled()
		expect(axios.get).toHaveBeenCalledTimes(1)
	})
})

再来看下 mockReturnValue

const getUserName = require('./user')
const axios = reuqire('axios')

jest.mock('axios')
// 这里替换对应的实现,修改结果
axios.get.mockReturnValue(Promise.resolve({ data: {username: 'Lee'} }))

// 测试某个api模块
it('test with mock modules', () => {
	return getUserName(1).then(name => {
		console.log(name); // Lee
		expect(axios.get).toHaveBeenCalled()
		expect(axios.get).toHaveBeenCalledTimes(1)
	})
})

再来看下 mockResolvedValue

const getUserName = require('./user')
const axios = reuqire('axios')

jest.mock('axios')
// 这里替换对应的实现,修改结果
axios.get.mockReturnValue({ data: {username: 'Lee'} })

// 测试某个api模块
it('test with mock modules', () => {
	return getUserName(1).then(name => {
		console.log(name); // Lee
		expect(axios.get).toHaveBeenCalled()
		expect(axios.get).toHaveBeenCalledTimes(1)
	})
})
  • 如果几个测试文件都要 mock axios 的话,这样就比较繁琐,axios 提供一个一劳永逸的方法
  • 只需要在需要的地方,创建 __mocks__ 的文件夹, 创建 __mocks__/axios.js,将自己要实现的逻辑写在里面
  • 比如

__mocks__/axios.js

const axios = {
	get: jest.fn(() => Promise.resolve({data: {username: 'mock'}}))
}
module.exports = axios

之后修改 mock.test.js

const getUserName = require('./user')
const axios = reuqire('axios')

// jest.mock('axios')
// axios.get.mockReturnValue({ data: {username: 'Lee'} })

// 测试某个api模块
it('test with mock modules', () => {
	return getUserName(1).then(name => {
		console.log(name); // Lee
		expect(axios.get).toHaveBeenCalled()
		expect(axios.get).toHaveBeenCalledTimes(1)
	})
})

上面两行被注释,直接使用定义好的 axios.get 即可

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值