编写第一个单元测试
这里通过vue-cli脚手架搭建一个项目,并使用eslint进行代码规范,创建一个sum.js文件,并进行测试sum.test.js
//创建并导出一个sum.js文件
function sum(a, b) {
return a + b;
}
module.exports = sum;
//编写sum.test.js文件,并进行测试
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
遇见的问题
由于代码使用的eslint进行代码规范的,test和expect只使用了一次,就会出现错误,所以我们需要再eslint的配置文件中添加忽略文件
"globals":{
"test": true,
"expect": true,
"window": true
},
使用匹配器
// 测试值是否匹配
test('object assignment', () => {
const data = { one: 1 }
data['two'] = 2
expect(data).toEqual({ one: 1, two: 2 })
})
// 测试相反的匹配
test('adding positive numbers is not zero', () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0)
}
}
})
// jest的匹配
// 测试真实性
test('null', () => {
const n = null
expect(n).toBeNull()// 只匹配null
expect(n).toBeDefined()// 只匹配 undefined
expect(n).not.toBeUndefined()// 与toBeUndefined相反
expect(n).not.toBeTruthy()// 匹配任何if语句为真
expect(n).toBeFalsy()// 匹配任何if语句为假
})
test('zero', () => {
const z = 0
expect(z).not.toBeNull()
expect(z).toBeDefined()
expect(z).not.toBeUndefined()
expect(z).not.toBeTruthy()
expect(z).toBeFalsy()
})
// 测试数字
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)
})
// 测试数字的时候对于浮点数近似,使用toBeClose而不是toEqual
test('两个浮点数字相加', () => {
const value = 0.1 + 0.2
// expect(value).toBe(0.3); 这句会报错,因为浮点数有舍入误差
expect(value).toBeCloseTo(0.3) // 这句可以运行
})
// 弦乐
test('there is no I in team', () => {
expect('team').not.toMatch(/I/)
})
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/)
})
多次重复设置
如果有多次重复测试就需要使用钩子函数,beforeEach和afterEach,假设我们要在每个测试之前都测试调用方法,或者是在每个测试之后测试调用方法,就需要使用到他们两个钩子函数
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
beforeEach和,afterEach能够通过与异步代码测试相同的方式处理异步代码— —他们可以采用done参数或返回一个诺言。例如,如果initializeCityDatabase()返回解决数据库初始化时的诺言,我们会想返回这一诺言:
beforeEach(() => {
return initializeCityDatabase();
});
一次性设置
当设置是异步的时,这尤其麻烦,因此您不能内联地进行设置。在某些情况下,您只需要在文件的开头做一次设置。开玩笑提供beforeAll和afterAll处理这种情况。
例如,如果initializeCityDatabase和clearCityDatabase都返回了promise,城市数据库可以在测试中重用,我们就能把我们的测试代码改成这样:
beforeAll(() => {
return initializeCityDatabase();
});
afterAll(() => {
return clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
关于测试作用域问题
默认情况下before和after的块可以应用到文件中的每个此时,此外可以通过describe块将测试分组,当before或者是after被使用在测试套件(describe)中的时候,他的作用域就只在他当前的作用域中合作,但是如果有顶级的before还有after,他的作用域是最大的
describe和test的执行顺序
Jest会在所有真正的测试开始之前执行测试文件里所有的describe处理程序(handlers)。这是在before和after处理程序里面(而不是在describe块中)进行准备工作和整理工作的另一个原因。块运行完后,,可能情况下,最好会按收集阶段遇到的顺序依次进行测试,依次运行所有测试,,等待每一个测试完成并整理好,然后才继续往下走。
也就是说,先同步执行所有的describe套件(包括内嵌的),然后执行所有的test测试实例
快照测试
快照测试主要用于UI的回归测试,第一次运行测试的时候,会在tests目录下生成snapshots目录,以及相应的组件并以snap为扩展名,之后每次改动,都会在测试中标记出来,如果是自己想要的变动,可以运行jest--updateSnapshot来更新快照,值得注意的是,UI测试以来react-test-render模块,并且要在引入react-native之后
ES6 class Mock
ES6是带有一些语法糖的构造函数,因此,ES6类的任何模拟都必须是一个函数,或一个实际的ES6类,因此您可以在模拟功能中模拟他们
创建ES6类模拟的四种方法
自动模拟
调用jest.mock('./sound-player')返回一个有用的自动模拟,您可以用来监视对类构造函数及其所有方法的调用,它将ES6类替换为模拟构造函数,并将其所有的方法替换为始终返回的模拟函数,方法调用保存在theAutomaticMock.mock.instances[index].methodName.mock.calls,如果您在类中使用箭头函数,则他们不会成为Mock的一部分,这么做的原因是他们只是持有该函数的属性
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
jest.mock('./sound-player'); // SoundPlayer is now a mock constructor
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
SoundPlayer.mockClear();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
// Show that mockClear() is working:
expect(SoundPlayer).not.toHaveBeenCalled();
const soundPlayerConsumer = new SoundPlayerConsumer();
// Constructor should have been called again:
expect(SoundPlayer).toHaveBeenCalledTimes(1);
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
// mock.instances is available with automatic mocks:
const mockSoundPlayerInstance = SoundPlayer.mock.instances[0];
const mockPlaySoundFile = mockSoundPlayerInstance.playSoundFile;
expect(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
// Equivalent to above check:
expect(mockPlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
expect(mockPlaySoundFile).toHaveBeenCalledTimes(1);
});
手动模拟
通过将模拟实现保存在文件夹中来创建手动模拟_mocks_,这使您可以指定实现,并且可以在测试文件中使用他
用模块工厂参数调用
jest.mock(path,moduleFactory)接受模块工厂参数,模块工厂是一个返回模拟的函数,为了模拟构造函数,模拟工厂必须返回构造函数,换句话说,模拟工厂必须是返回函数的函数--高阶函数
import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};//返回的变量名必须是Mock开头的函数名
});
});
在工厂函数中,模块工厂功能必须返回一个功能,也就是说,jest.mock()传的第二个参数不能是一个箭头函数,必须是一个ES5中的构造函数
使用mockImplementation()或替换模拟mockImplementationOnce()
可以通过mockImplementation()现有的模拟来替换上述所有的模拟,以更改单个测试或者是所有测试的实现
跟踪使用情况
在上面我们详细的描述了构造函数的模拟情况,以及模拟方式,但是,对于测试人员来说,不仅仅只想要测试构造函数是否成功,最重要的是传入的参数是否正确,所以我们还要对构造函数进行追踪监视
监视构造函数
为了跟踪对构造函数的调用,请用jest模拟函数替换高阶函数,使用创建他jest.fn(),然后使用指定他的而实现mockImplementation()。
import SoundPlayer from './sound-player';
jest.mock('./sound-player', () => {
// Works and lets you check for constructor calls:
return jest.fn().mockImplementation(() => {
return {playSoundFile: () => {}};
});
});