中国优秀设计网站有哪些书签制作简单漂亮

diannao/2026/1/16 6:16:23/文章来源:
中国优秀设计网站有哪些,书签制作简单漂亮,出口企业网站建设,免费域名空间注册大家好#xff0c;我是若川。今天分享一篇vue项目如何做单元测试的好文#xff0c;文章比较长#xff0c;建议先收藏#xff0c;需要时用电脑看。点击下方卡片关注我、加个星标学习源码系列、年度总结、JS基础系列关于单元测试#xff0c;最常见的问题应该就是“前端单元测… 大家好我是若川。今天分享一篇vue项目如何做单元测试的好文文章比较长建议先收藏需要时用电脑看。点击下方卡片关注我、加个星标学习源码系列、年度总结、JS基础系列关于单元测试最常见的问题应该就是“前端单元测试有必要吗”通过这篇文章你将会了解单元测试的必要性以及在Vue项目中如何能够全面可靠的测试我们写的组件。单元测试的必要性一般在我们的印象里单元测试都是测试工程师的工作前端负责代码就行了百度搜索Vue单元测试联想词出来的都是“单元测试有必要吗” “单元测试是做什么的”虽然我们平时项目中一般都会有测试工程师来对我们的页面进行测试“兜底”但是根据我的观察一般测试工程师并不会覆盖所有的业务逻辑而且有一些深层次的代码逻辑测试工程师在不了解代码的情况下也根本无法进行触发。因此在这种情况下我们并不能够完全的依赖测试工程师对我们项目测试前端项目的单元测试就显得非常的有必要。而且单元测试也能够帮助我们节省很大一部分自我测试的成本假如我们有一个订单展示的组件根据订单状态的不同以及其他的一些业务逻辑来进行对应文案的展示我们想在页面上查看文案展示是否正确这时就需要繁琐的填写下单信息后才能查看如果第二天又又加入了一些新的逻辑判断你前一天下的单早就过期啦这时你有三个选择第一种选择就是再次繁琐地填写订单并支付完又给老板提供资金支持了第二种选择就是死皮赖脸的求着后端同事给你更改订单状态后端同事给你一个白眼自己体会第三种选择就是代理接口或者使用mock数据你需要编译整个项目运行进行测试。这时单元测试就提供了第四种成本更低的测试方式写一个测试用例来对我们的组件进行测试判断文案是否按照我们预想的方式进行展示这种方式既不需要依赖后端的协助也不需要对项目进行任何改动可谓是省时又省力。测试框架和断言库说到单元测试我们首先来介绍一下流行的测试框架主要是mocha和jest。先简单介绍下mocha翻译成中文就是摩卡人家是一种咖啡不是抹茶啊名字的由来估猜是因为开发人员喜欢喝摩卡咖啡就像Java名字也是从咖啡由来一样mocha的logo也是一杯摩卡咖啡mocha logo和jest相比两者主要的不同就是jest内置了集成度比较高的断言库expect.js而mocha需要搭配额外的断言库一般会选择比较流行的chai作为断言库这里一直提到断言库那么什么是断言库呢我们首先来看下mocha是怎么来测试代码的首先我们写了一个addNum函数但是不确定是否返回我们想要的结果因此需要对这个函数进行测试//src/index.js function addNum(a, b) {return a  b; } module.exports  addNum; 然后就可以写我们的测试文件了所有的测试文件都放在test目录下一般会将测试文件和所要测试的源码文件同名方便进行对应运行mocha时会自动对test目录下所有js文件进行测试//test/index.test.js var addNum  require(../src/index); describe(测试addNum函数, ()  {it(两数相加结果为两个数字的和, ()  {if (addNum(1, 2) ! 3) {throw new Error(两数相加结果不为两个数字的和);}}); }); 上面这段代码就是测试脚本的语法一个测试脚本会包括一个或多个describe块每个describe又包括一个或多个it块这里describe称为测试套件test suite表示一组相关的测试它包含了两个参数第一个参数是这个测试套件的名称第二个参数是实际执行的函数。而it称为测试用例表示一个单独的测试是测试的最小单位它也包含两个参数第一个参数是测试用例的名称第二个参数是实际执行的函数。it块中就是我们需要测试的代码如果运行结果不是我们所预期的就抛出异常上面的测试用例写好后我们就可以运行测试了运行mocha运行结果通过了是我们想要的结果说明我们的函数是正确的但是每次都通过抛出异常来判断多少有点繁琐了断言库就出现了断言的目的就是将测试代码运行后和我们的预期做比较如果和预期一致就表明代码没有问题如果和预期不一致就是代码有问题了每一个测试用例最后都会有一个断言进行判断如果没有断言测试就没有意义了。上面也说了mocha一般搭配chai断言库而chai有好几种断言风格比较常见的有should和expect两种风格我们分别看下这两种断言var chai  require(chai),expect  chai.expect,should  chai.should();describe(测试addNum函数, ()  {it(12, ()  {addNum(1, 2).should.equal(3);});it(23, ()  {expect(addNum(2, 3)).to.be.equal(5);}); }); 这里should是后置的在断言变量之后而expect是前置的作为断言的开始两种风格纯粹看个人喜好我们发现这里expect是从chai中获取的一个函数而should则是直接调用这是因为should实际上是给所有的对象都扩充了一个 getter 属性should因此我们才能够在变量上使用.should方式来进行断言。和chai的多种断言风格不同jest内置了断言库expect它的语法又有些不同describe(测试addNum函数, ()  {it(12, ()  {expect(addNum(1, 2)).toBe(3);});it(23, ()  {expect(addNum(2, 3)).toBe(5);}); }); jest中的expect直接通过toBe的语法在形式上相较于mocha更为简洁这两个框架在使用上极其相似比如在异步代码上都支持done回调和async/await关键字在断言语法和其他用法有些差别两者也有相同的钩子机制连名字都相同beforeEach和afterEach在vue cli脚手架创建项目时也可以在两个框架中进行选择其一我们这里主要以jest进行测试。JestJest是Facebook出品的一个测试框架相较于其他测试框架最大的特点就是内置了常用的测试工具比如自带断言、测试覆盖率工具实现了开箱即用这也和它官方的slogan相符。jest logo❝Jest 是一个令人愉快的 JavaScript 测试框架专注于简洁明快。❞Jest几乎是零配置的它会自动识别一些常用的测试文件比如*.spec.js和 *.test.js后缀的测试脚本所有的测试脚本都放在tests或__tests__目录下我们可以在全局安装jest或者局部安装然后在packages.json中指定测试脚本{scripts: {test: jest} } 当我们运行npm run test时会自动运行测试目录下所有测试文件完成测试我们在jest官网可能还会看到通过test函数写的测试用例test(12, ()  {expect(addNum(1, 2)).toBe(3); }); 和it函数相同test函数也代表一个测试用例mocha只支持it而jest支持it和test这里为了和jest官网保持统一下面代码统一使用test函数。匹配器我们经常需要对测试代码返回的值进行匹配测试上面代码中的toBe是最简单的一个匹配器用来测试两个数值是否相同。test(test tobe, ()  {expect(2  2).toBe(4);expect(true).toBe(true);const val  team;expect(val).toBe(team);expect(undefined).toBe(undefined);expect(null).toBe(null); }); toBe函数内部使用了Object.is来进行精确匹配它的特性类似于对于普通类型的数值可以进行比较但是对于对象数组等复杂类型就需要用到toEqual来比较了    test(expect a object, ()  {var obj  {a: 1,};obj.b  2;expect(obj).toEqual({ a: 1, b: 2 }); });test(expect array, ()  {var list  [];list.push(1);list.push(2);expect(list).toEqual([1, 2]); }); 我们有时候还需要对undefined、null等类型或者对条件语句中的表达式的真假进行精确匹配Jest也有五个函数帮助我们toBeNull只匹配nulltoBeUndefined只匹配undefinedtoBeDefined与toBeUndefined相反等价于.not.toBeUndefinedtoBeTruthy匹配任何 if 语句为真toBeFalsy匹配任何 if 语句为假test(null, ()  {const n  null;expect(n).toBeNull();expect(n).not.toBeUndefined();expect(n).toBeDefined();expect(n).not.toBeTruthy();expect(n).toBeFalsy(); }); test(0, ()  {const z  0;expect(z).not.toBeNull();expect(z).not.toBeUndefined();expect(z).toBeDefined();expect(z).not.toBeTruthy();expect(z).toBeFalsy(); }); test(undefined, ()  {const a  undefined;expect(a).not.toBeNull();expect(a).toBeUndefined();expect(a).not.toBeDefined();expect(a).not.toBeTruthy();expect(a).toBeFalsy(); }); toBeTruthy和toBeFalsy用来判断在if语句中的表达式是否成立等价于if(n)和if(!n)的判断。对于数值类型的数据我们有时候也可以通过大于或小于来进行判断test(number, ()  {const val  2  2;// 大于expect(val).toBeGreaterThan(3);// 大于等于expect(val).toBeGreaterThanOrEqual(3.5);// 小于expect(val).toBeLessThan(5);// 小于等于expect(val).toBeLessThanOrEqual(4.5);// 完全判断expect(val).toBe(4);expect(val).toEqual(4); }); 浮点类型的数据虽然我们也可以用toBe和toEqual来进行比较但是如果遇到有些特殊的浮点数据计算比如0.10.2就会出现问题我们可以通过toBeCloseTo来判断test(float, ()  {// expect(0.1  0.2).toBe(0.3); 报错expect(0.1  0.2).toBeCloseTo(0.3); }); 对于数组、set或者字符串等可迭代类型的数据可以通过toContain来判断内部是否有某一项test(expect iterable, ()  {const shoppingList  [diapers,kleenex,trash bags,paper towels,milk,];expect(shoppingList).toContain(milk);expect(new Set(shoppingList)).toContain(diapers);expect(abcdef).toContain(cde); }); 异步代码我们项目中经常也会涉及到异步代码比如setTimeout、接口请求等都会涉及到异步那么这些异步代码怎么来进行测试呢假设我们有一个异步获取数据的函数fetchDataexport function fetchData(cb) {setTimeout(()  {cb(res data);}, 2000); } 在2秒后通过回调函数返回了一个字符串我们可以在测试用例的函数中使用一个done的参数Jest会等done回调后再完成测试test(callback, (done)  {function cb(data) {try {expect(data).toBe(res data);done();} catch (error) {done();}}fetchData(cb); }); 我们将一个回调函数传入fetchData在回调函数中对返回的数据进行断言在断言结束后需要调用done如果最后没有调用done那么Jest不知道什么时候结束就会报错在我们日常代码中都会通过promise来获取数据将我们的fetchData进行一下改写export function fetchData() {return new Promise((resolve, reject)  {setTimeout(()  {resolve(promise data);}, 2000);}); } Jest支持在测试用例中直接返回一个promise我们可以在then中进行断言test(promise callback, ()  {return fetchData().then((res)  {expect(res).toBe(promise data);}); }); 除了直接将fetchData返回我们也可以在断言中使用.resolves/.rejects匹配符Jest也会等待promise结束test(promise callback, ()  {return expect(fetchData()).resolves.toBe(promise data); }); 除此之外Jest还支持async/await不过我们需要在test的匿名函数加上async修饰符表示test(async/await callback, async ()  {const data  await fetchData();expect(data).toBe(promise data); }); 全局挂载与卸载全局挂载和卸载有点类似Vue-Router的全局守卫在每个导航触发前和触发后做一些操作在Jest中也有比如我们需要在每个测试用例前初始化一些数据或者在每个测试用例之后清除数据就可以使用beforeEach和afterEachlet cityList  [] beforeEach(()  {initializeCityDatabase(); });afterEach(()  {clearCityDatabase(); });test(city data has suzhou, ()   {expect(cityList).toContain(suzhou) })test(city data has shanghai, ()   {expect(cityList).toContain(suzhou) }) 这样每个测试用例进行测试前都会调用init每次结束后都会调用clear我们有可能会在某些test中更改cityList的数据但是在beforeEach进行初始化的操作后每个测试用例获取的cityList数据就保证都是相同的和上面一节异步代码一样在beforeEach和afterEach我们也可以使用异步代码来进行初始化let cityList  [] beforeEach(()  {return initializeCityDatabase().then((res){cityList  res.data}); }); //或者使用async/await beforeEach(async ()  {cityList  await initializeCityDatabase(); }); 和beforeEach和afterEach相对应的就是beforeAll和afterAll区别就是beforeAll和afterAll只会执行一次beforeEach和afterEach默认会应用到每个test但是我们可能希望只针对某些test我们可以通过describe将这些test放到一起这样就只应用到describe块中的testbeforeEach(()  {// 应用到所有的test }); describe(put test together, ()  {beforeEach(()  {// 只应用当前describe块中的test});test(test1, () {})test(test2, () {}) }); 模拟函数在项目中一个模块的函数内常常会去调用另外一个模块的函数。在单元测试中我们可能并不需要关心内部调用的函数的执行过程和结果只想知道被调用模块的函数是否被正确调用甚至会指定该函数的返回值因此模拟函数十分有必要。如果我们正在测试一个函数forEach它的参数包括了一个回调函数作用在数组上的每个元素export function forEach(items, callback) {for (let index  0; index  items.length; index) {callback(items[index]);} } 为了测试这个forEach我们需要构建一个模拟函数来检查模拟函数是否按照预期被调用了test(mock callback, ()  {const mockCallback  jest.fn((x)  42  x);forEach([0, 1, 2], mockCallback);expect(mockCallback.mock.calls.length).toBe(3);expect(mockCallback.mock.calls[0][0]).toBe(0);expect(mockCallback.mock.calls[1][0]).toBe(1);expect(mockCallback.mock.calls[2][0]).toBe(1);expect(mockCallback.mock.results[0].value).toBe(42); }); 我们发现在mockCallback有一个特殊的.mock属性它保存了模拟函数被调用的信息我们打印出来看下mock属性它有四个属性calls调用参数instancesthis指向invocationCallOrder函数调用顺序results调用结果在上面属性中有一个instances属性表示了函数的this指向我们还可以通过bind函数来更改我们模拟函数的thistest(mock callback, ()  {const mockCallback  jest.fn((x)  42  x);const obj  { a: 1 };const bindMockCallback  mockCallback.bind(obj);forEach([0, 1, 2], bindMockCallback);expect(mockCallback.mock.instances[0]).toEqual(obj);expect(mockCallback.mock.instances[1]).toEqual(obj);expect(mockCallback.mock.instances[2]).toEqual(obj); }); 通过bind更改函数的this之后我们可以用instances来进行检测模拟函数可以在运行时将返回值进行注入const myMock  jest.fn(); // undefined console.log(myMock());myMock.mockReturnValueOnce(10).mockReturnValueOnce(x).mockReturnValue(true);//10 x true true console.log(myMock(), myMock(), myMock(), myMock());myMock.mockReturnValueOnce(null);// null true true console.log(myMock(), myMock(), myMock()); 我们第一次执行myMock由于没有注入任何返回值然后通过mockReturnValueOnce和mockReturnValue进行返回值注入Once只会注入一次模拟函数在连续性函数传递返回值时使用注入非常的有用const filterFn  jest.fn(); filterFn.mockReturnValueOnce(true).mockReturnValueOnce(false); const result  [2, 3].filter((num)  filterFn(num)); expect(result).toEqual([2]); 我们还可以对模拟函数的调用情况进行断言const mockFunc  jest.fn();// 断言函数还没有被调用 expect(mockFunc).not.toHaveBeenCalled(); mockFunc(1, 2); mockFunc(2, 3); // 断言函数至少调用一次 expect(mockFunc).toHaveBeenCalled(); // 断言函数调用参数 expect(mockFunc).toHaveBeenCalledWith(1, 2); expect(mockFunc).toHaveBeenCalledWith(2, 3); // 断言函数最后一次的调用参数 expect(mockFunc).toHaveBeenLastCalledWith(2, 3); 除了能对函数进行模拟Jest还支持拦截axios返回数据假如我们有一个获取用户的接口// /src/api/users const axios  require(axios);function fetchUserData() {return axios.get(/user.json).then((resp)  resp.data); }module.exports  {fetchUserData, }; 现在我们想要测试fetchUserData函数获取数据但是并不实际请求接口我们可以使用jest.mock来模拟axios模块const users  require(../api/users); const axios  require(axios); jest.mock(axios);test(should fetch users, ()  {const userData  {name: aaa,age: 10,};const resp  { data: userData };axios.get.mockResolvedValue(resp);return users.fetchUserData().then((res)  {expect(res).toEqual(userData);}); }); 一旦我们对模块进行了模拟我们可以用get函数提供一个mockResolvedValue方法以返回我们需要测试的数据通过模拟后实际上axios并没有去真正发送请求去获取/user.json的数据。Vue Test UtilsVue Test Utils是Vue.js官方的单元测试实用工具库能够对我们编写的Vue组件进行测试。挂载组件在Vue中我们通过import引入组件然后在components进行注册后就能使用在单元测试中我们使用mount来进行挂载组件假如我们写了一个计数器组件counter.js用来展示count并且有一个按钮操作count!-- Counter.vue -- templatediv classcounterspan classcount{{ count }}/spanbutton idadd clickadd加/button/div /template script export default {data() {return {count: 0,};},methods: {add() {this.count;},}, }; /script 组件进行挂载后得到一个wrapper包裹器wrapper会暴露很多封装、遍历和查询其内部的Vue组件实例的便捷的方法。import { mount } from vue/test-utils; import Counter from /components/Counter; const wrapper  mount(Counter); const vm  wrapper.vm; 我们可以通过wrapper.vm来访问组件的Vue实例进而获取实例上的methods和data等通过wrapper我们可以对组件的渲染情况做断言// test/unit/counter.spec.js describe(Counter, ()  {const wrapper  mount(Counter);test(counter class, ()  {expect(wrapper.classes()).toContain(counter);expect(wrapper.classes(counter)).toBe(true);});test(counter has span, ()  {expect(wrapper.html()).toContain(span classcount0/span);});test(counter has btn, ()  {expect(wrapper.find(button#add).exists()).toBe(true);expect(wrapper.find(button#add).exists()).not.toBe(false);}); }); 上面几个函数我们根据名字也能猜出它们的作用classes获取wrapper的class并返回一个数组html获取组件渲染html结构字符串find返回匹配子元素的wrapperexists断言wrapper是否存在find返回的是查找的第一个DOM节点但有些情况我们希望能操作一组DOM我们可以用findAll函数const wrapper  mount(Counter); // 返回一组wrapper const divList  wrapper.findAll(div); divList.length // 找到第一个div返回它的wrapper const firstDiv  divList.at(0); 有些组件需要通过外部传入的props、插槽slots、provide/inject等其他的插件或者属性我们在mount挂载时可以传入一个对象设置这些额外属性const wrapper  mount(Component, {// 向组件传入data合并到现有的data中data() {return {foo: bar}},// 设置组件的propspropsData: {msg: hello},// vue本地拷贝localVue,// 伪造全局对象mocks: {$route},// 插槽// 键名就是相应的 slot 名// 键值可以是一个组件、一个组件数组、一个字符串模板或文本。slots: {default: SlotComponent,foo: div /,bar: my-component /,baz: },// 用来注册自定义组件stubs: {my-component: MyComponent,el-button: true,},// 设置组件实例的$attrs 对象。attrs: {},// 设置组件实例的$listeners对象。listeners: {click: jest.fn()},// 为组件传递用于注入的属性provide: {foo() {return fooValue}} }) stubs主要用来处理在全局注册的自定义组件比如我们常用的组件库Element等直接使用el-button、el-input组件或者vue-router注册在全局的router-view组件等当我们在单元测试中引入时就会提示我们对应的组件找不到这时我们就可以通过这个stubs来避免报错。我们在对某个组件进行单元测试时希望只针对单一组件进行测试避免子组件带来的副作用比如我们在父组件ParentComponent中判断是否有某个div时恰好子组件ChildComponent也渲染了该div那么就会对我们的测试带来一定的干扰我们可以使用shallowMount挂载函数相遇比mountshallowMount不会渲染子组件import { shallowMount } from vue/test-utils const wrapper  shallowMount(Component) 这样就保证了我们需要测试的组件在渲染时不会渲染其子组件避免子组件的干扰。操作组件我们经常需要对子组件中的元素或者子组件的数据进行一些操作和修改比如页面的点击、修改data数据进行操作后再来断言数据是否正确我们以一个简单的Form组件为例templatediv classformdiv classtitle{{ title }}/divdivspan请填写姓名/spaninput typetext idname-input v-modelname /div classname{{ name }}/div/divdivspan请选择性别/spaninput typeradio namesex v-modelsex valuef id /input typeradio namesex v-modelsex valuem id //divdivspan请选择爱好/spanfootbalinputtypecheckboxnamehobbyv-modelhobbyvaluefootbal/basketballinputtypecheckboxnamehobbyv-modelhobbyvaluebasketball/skiinput typecheckbox namehobby v-modelhobby valueski //divdivinput:classsubmit ? submit : typesubmitvalue提交clickclickSubmit//div/div /template script export default {name: Form,props: {title: {type: String,default: 表单名称,},},data() {return {name: ,sex: f,hobby: [],submit: false,};},methods: {clickSubmit() {this.submit  !this.submit;},}, }; /script 我们可以向Form表单组件传入一个title作为表单的名称其内部也有input、radio和checkbox等一系列元素我们就来看下怎么对这些元素进行修改首先我们来修改props的值在组件初始化的时候我们传入了propsData在后续的代码中我们可以通过setProps对props值进行修改const wrapper  mount(Form, {propsData: {title: form title,}, }); const vm  wrapper.vm; test(change prop, ()  {expect(wrapper.find(.title).text()).toBe(form title);wrapper.setProps({title: new form title,});// 报错了expect(wrapper.find(.title).text()).toBe(new form title); }); 我们满怀期待进行测试但是发现最后一条断言报错了这是因为Vue异步更新数据我们改变prop和data后获取dom发现数据并不会立即更新在页面上我们一般都会通过$nextTick进行解决在单元测试时我们也可以使用nextTick配合获取DOMtest(change prop1, async ()  {expect(wrapper.find(.title).text()).toBe(new form title);wrapper.setProps({title: new form title1,});await Vue.nextTick();// 或者使用vm的nextTick// await wrapper.vm.nextTick();expect(wrapper.find(.title).text()).toBe(new form title1); });test(change prop2, (done)  {expect(wrapper.find(.title).text()).toBe(new form title1);wrapper.setProps({title: new form title2,});Vue.nextTick(()  {expect(wrapper.find(.title).text()).toBe(new form title2);done();}); }); 和Jest中测试异步代码一样我们也可以使用done回调或者async/await来进行异步测试除了设置propssetData可以用来改变wrapper中的datatest(test set data, async ()  {wrapper.setData({name: new name,});expect(vm.name).toBe(new name);await Vue.nextTick();expect(wrapper.find(.name).text()).toBe(new name); }); 对于input、textarea或者select这种输入性的组件元素我们有两种方式来改变他们的值test(test input set value, async ()  {const input  wrapper.find(#name-input);await input.setValue(change input by setValue);expect(vm.name).toBe(change input by setValue);expect(input.element.value).toBe(change input by setValue); }); // 等价于 test(test input trigger, ()  {const input  wrapper.find(#name-input);input.element.value  change input by trigger;// 通过input.element.value改变值后必须触发trigger才能真正修改input.trigger(input);expect(vm.name).toBe(change input by trigger); }); 可以看出通过input.element.value或者setValue的两种方式改变值后由于v-model绑定关系因此vm中的data数据也进行了改变我们还可以通过input.element.value来获取input元素的值。对于radio、checkbox选择性的组件元素我们可以通过setChecked(Boolean)函数来触发值的更改更改同时也会更新元素上v-model绑定的值test(test radio, ()  {expect(vm.sex).toBe(f);const radioList  wrapper.findAll(input[namesex]);radioList.at(1).setChecked();expect(vm.sex).toBe(m); }); test(test checkbox, ()  {expect(vm.hobby).toEqual([]);const checkboxList  wrapper.findAll(input[namehobby]);checkboxList.at(0).setChecked();expect(vm.hobby).toEqual([footbal]);checkboxList.at(1).setChecked();expect(vm.hobby).toEqual([footbal, basketball]);checkboxList.at(0).setChecked(false);expect(vm.hobby).toEqual([basketball]); }); 对于按钮等元素我们希望在上面触发点击操作可以使用trigger进行触发test(test click, async ()  {const submitBtn  wrapper.find(input[typesubmit]);await submitBtn.trigger(click);expect(vm.submit).toBe(true);await submitBtn.trigger(click);expect(vm.submit).toBe(false); }); 自定义事件对于一些组件可能会通过$emit触发一些返回数据比如我们改写上面Form表单中的submit按钮点击后返回一些数据{methods: {clickSubmit() {this.$emit(foo, foo1, foo2);this.$emit(bar, bar1);},}, } 除了触发组件中元素的点击事件进行$emi我们还可以通过wrapper.vm触发因为vm本身相当于组件的thiswrapper.vm.$emit(foo, foo3); 最后所有$emit触发返回的数据都存储在wrapper.emitted()它返回了一个对象结构如下{foo: [ [ foo1, foo2 ], [ foo3 ] ],bar: [ [ bar1 ] ] } emitted()返回对象中的属性是一个数组数组的length代表了这个方法被触发了多少次我们可以对对象上的属性进行断言来判断组件的emit是否被触发test(test emit, async ()  {// 组件元素触发emitawait wrapper.find(input[typesubmit]).trigger(click);wrapper.vm.$emit(foo, foo3);await vm.$nextTick();// foo被触发过expect(wrapper.emitted().foo).toBeTruthy();// foo触发过两次expect(wrapper.emitted().foo.length).toBe(2);// 断言foo第一次触发的数据expect(wrapper.emitted().foo[0]).toEqual([foo1, foo2]);// baz没有触发expect(wrapper.emitted().baz).toBeFalsy(); }); 我们也可以把emitted()函数进行改写并不是一次性获取整个emitted对象expect(wrapper.emitted(foo)).toBeTruthy(); expect(wrapper.emitted(foo).length).toBe(2); 有一些组件触发emit事件可能是由其子组件触发的我们可以通过子组件的vm进行emitimport { mount } from vue/test-utils import ParentComponent from /components/ParentComponent import ChildComponent from /components/ChildComponentdescribe(ParentComponent, ()  {it(emit, ()  {const wrapper  mount(ParentComponent)wrapper.find(ChildComponent).vm.$emit(custom)}) }) 配合Vue-Router在有些组件中我们有可能会用到Vue-Router的相关组件或者Api方法比如我们有一个Header组件templatedivdiv clickjump{{ $route.params.id }}/divrouter-link :to{ path: /detail }/router-linkrouter-view/router-view/div /template script export default {data() {return {};},mounted() {},methods: {jump() {this.$router.push({path: /list,});},}, }; /script 直接在测试脚本中引入会报错提示找不到router-link和router-view两个组件和$route属性这里不推荐使用Vue.use(VueRouter)因为会污染全局的Vue我们有两种方法解决第一种使用createLocalVue创建一个Vue的类我们可以在这个类中进行添加组件、混入和安装插件而不会污染全局的Vue类import { shallowMount, createLocalVue } from vue/test-utils import VueRouter from vue-router import Header from /components/Header;// 一个Vue类 const localVue  createLocalVue() localVue.use(VueRouter) // 路由数组 const routes  [] const router  new VueRouter({routes })shallowMount(Header, {localVue,router }) 我们来看下这里做了哪些操作通过createLocalVue创建了一个localVue相当于import Vue然后localVue.use告诉Vue来使用VueRouter和Vue.use有着相同的作用最后实例化创建router对象传入shallowMount进行挂载。第二种方式是注入伪造数据这里主要用的就是mocks和stubsmocks用来伪造和router等全局对象是一种将属性添加到Vue.prototype上的方式而stubs用来覆写全局或局部注册的组件import { mount } from vue/test-utils; import Header from /components/Header;describe(header, ()  {const $route  {path: /home,params: {id: 111,},};const $router  {push: jest.fn(),};const wrapper  mount(Header, {stubs: [router-view, router-link],mocks: {$route,$router,},});const vm  wrapper.vm;test(render home div, ()  {expect(wrapper.find(div).text()).toBe(111);}); }); 相比于第一种方式第二种方式可操作性更强可以直接伪造$route路由的数据一般第一种方式不会单独使用经常会搭配第二种伪造数据的方式。配合Vuex我们通常会在组件中会用到vuex我们可以通过伪造store数据来模拟测试假如我们有一个的count组件它的数据存放在vuex中templatedivdiv classnumber{{ number }}/divdiv classadd clickclickAddadd/divdiv classsub clickclickSubsub/div/div /template script import { mapState, mapGetters } from vuex; export default {name: Count,computed: {...mapState({number: (state)  state.number,}),},methods: {clickAdd() {this.$store.commit(ADD_COUNT);},clickSub() {this.$store.commit(SUB_COUNT);},}, }; /script 在vuex中我们通过mutations对number进行修改export default new Vuex.Store({state: {number: 0,},mutations: {ADD_COUNT(state) {state.number  state.number  1;},SUB_COUNT(state) {state.number  state.number - 1;},} }); 那我们现在如何来伪造store数据呢这里和Vue-Router的原理是一样的通过createLocalVue创建一个隔离的Vue类import { mount, createLocalVue } from vue/test-utils; import Count from /components/Count; import Vuex from vuex;const localVue  createLocalVue(); localVue.use(Vuex);describe(count, ()  {const state  {number: 0,};const mutations  {ADD_COUNT: jest.fn(),SUB_COUNT: jest.fn(),};const store  new Vuex.Store({state,mutations});test(render, async ()  {const wrapper  mount(Count, {store,localVue,});expect(wrapper.find(.number).text()).toBe(0);wrapper.find(.add).trigger(click);expect(mutations.ADD_COUNT).toHaveBeenCalled();expect(mutations.SUB_COUNT).not.toHaveBeenCalled();}); }); 我们看一下这里做了什么操作前面和VueRouter一样创建一个隔离类localVue然后通过new Vuex.Store创建了一个store并填入假数据state和mutations这里我们并不关心mutations中函数做了哪些操作我们只要知道元素点击触发了哪个mutations函数通过伪造的函数我们去断言mutations是否被调用。另一种测试store数据的方式是创建一个运行中的store不再通过页面触发Vuex中的函数这样的好处就是不需要伪造Vuex函数假设我们有一个store/list.jsexport default {state: {list: [],},getters: {joinList: (state)  {return state.list.join(,);},},mutations: {PUSH(state, payload) {state.list.push(payload);},}, }; import { createLocalVue } from vue/test-utils; import Vuex from vuex; import { cloneDeep } from lodash; import listStore from /store/list;describe(list, ()  {test(expect list, ()  {const localVue  createLocalVue();localVue.use(Vuex);const store  new Vuex.Store(cloneDeep(listStore));expect(store.state.list).toEqual([]);store.commit(PUSH, 1);expect(store.state.list).toEqual([1]);});test(list getter, ()  {const localVue  createLocalVue();localVue.use(Vuex);const store  new Vuex.Store(cloneDeep(listStore));expect(store.getters.joinList).toBe();store.commit(PUSH, 1);store.commit(PUSH, 3);expect(store.getters.joinList).toBe(1,3);}); }); 我们直接创建了一个store通过store来进行commit和getters的操作。总结前端框架迭代不断但是前端单元测试确显有人关注一个健壮的前端项目应该有单元测试的模块保证了我们的项目代码质量和功能的稳定但是也并不是所有的项目都需要有单元测试的毕竟编写测试用例也需要成本因此如果你的项目符合下面的几个条件就可以考虑引入单元测试长期稳定的项目迭代需要保证代码的可维护性和功能稳定页面功能相对来说说比较复杂逻辑较多对于一些复用性很高的组件可以考虑单元测试最近组建了一个湖南人的前端交流群如果你是湖南人可以加我微信 ruochuan12 拉你进群。今日话题略。欢迎分享、收藏、点赞、在看我的公众号文章~一个愿景是帮助5年内前端人成长的公众号可加我个人微信 ruochuan12长期交流学习推荐阅读我在阿里招前端该怎么帮你可进面试群2年前端经验做的项目没技术含量怎么办点击上方卡片关注我、加个星标················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》多篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经写了7篇点击查看年度总结。同时活跃在知乎若川掘金若川。致力于分享前端开发经验愿景帮助5年内前端人走向前列。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/91547.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

帮彩票网站做流量提升商务网站建设教学视频教程

目录 基于脚手架创建前端工程 1、环境要求 2、操作过程 3、工程结构 4、启动前端服务 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法&#xff0c…

视频网站seo怎么做张家港企业网站

文章目录 一、索引不同1 InnoDB聚簇索引,MyISAM非聚簇索引1 InnoDB聚簇索引2 MyISAM非聚簇索引 2 InnoDB必须要有主键,MyISAM允许没有主键3 InnoDB支持外键4 InnoDB不支持全文索引5 索引保存位置不同 二、对事物的支持三、存储结构不同四、存储空间不同五…

长沙网站托管网络营销专业就业

文章目录 1. 简介2. iKuai 部署2.1 安装 VMware2.2 安装 iKuai(1) 下载固件(2) 安装 iKuai 虚拟机(3) 配置 iKuai 虚拟机(4) 配置 iKuai(5) 配置多拨分流 2.3 测试速度 3. Clash 部署3.1 准备工作(1) 配置磁盘分区(2) 安装 Docker(3) 安装 Clash(4) 设置代理 1. 简介 由于博主…

怎么做网站收录榆林网站开发公司

servlet的本质是什么,它是如何工作的?基于计算机和网络通讯(主要是http协议)构建网络应用的,具体的实现细节是怎样的? 查看https://www.zhihu.com/question/21416727

网站建设的栏目内容是网站总体策划的内容有哪些

这篇文章是我观看 Mosh 的 MySQL 完整版课程进行题目练习的记录,视频的话去 B 站搜索就能找到,数据库文件的话可以从这里下载。 目录第二章2- SELECT 子句3- WHERE 子句4- 逻辑运算符5- IN 运算符6- BETWEEN 运算符7- LIKE 运算符8- REGEXP 运算符&#…

学生管理系统 静态网站源码广州公司注册费用

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、下雨的特效1-1.首先就是创建一个自带的粒子系统,整几张贴图,设置一下就能实现想要的效果了1-2 接着往下看视频效果 👉二、下雪的特效👉三、下雪有积雪的效果3-1 先把控…

oppo网站开发设计手机网页游戏排行榜前十名

前言本文是为了帮大家快速回顾了Java中知识点,这套面试手册涵盖了诸多Java技术栈的面试题和答案,相信可以帮助大家在最短的时间内用作面试复习,能达到事半功倍效果。本来想将文件上传到github上,但由于文件太大有的都无法显示所以…

国内网站怎么做有效果网站收录问题

参考链接: Java中的关联 | 组合和聚合 一.引言 其实自己也不知道,在需求设计、架构设计、开发阶段是否真正需要弄明白“关联、聚合、组合”。原本计划这篇博文写“继承“和”多态”,但是一翻阅资料,基本上都会把“继承”和“组…

岳麓区营销型网站建设定制北京公司注销流程及费用

转载:http://blog.csdn.net/liufei_learning/article/details/19220391 理解Hash 哈希表(hash table)是从一个集合A到另一个集合B的映射(mapping)。 映射是一种对应关系,而且集合A的某个元素只能对应集合B中的一个元素。但反过来,集合B中的一…

15年做哪些网站能致富商标图案参考

2010年8月31日上午9点半,中软国际准员工培养计划—C开发/软件测试方向开班典礼在无锡ETC隆重举行。开班典礼在热烈的掌声中拉开了序幕。典礼由教务部经理陈晨老师主持,首先她对近20名学员的到来表示欢迎,并对本次学习班顺利开班表示祝贺。中软…

收纳用品网站建设wordpress镜像存储

来源:AI科技评论作者 | 张俊杰编辑 | 丛 末本文首发于知乎 https://zhuanlan.zhihu.com/p/143155437随着深度学习的快速发展,优秀的模型层出不穷,比如图像领域的ResNet、自然语言处理领域的Bert,这些革命性的新技术使得应用效果快…

关掉wordpress站点图片seo优化是什么意思

机器学习:从源数据清洗到特征工程建立谈金融反欺诈模型训练 本文旨在通过一个完整的实战例子,演示从源数据清洗到特征工程建立,再到模型训练,以及模型验证和评估的一个机器学习的完整流程。由于初识机器学习,会比较多的…

集团酒店网站建设唯品会 只做特卖的网站

https://stackoverflow.com/questions/41364386/whats-the-difference-between-markforcheck-and-detectchanges转载于:https://www.cnblogs.com/chen8840/p/10573295.html

需要网站建设的人多吗海南网新闻最新消息今天

以下内容是本人在开发过程中实际验证过的一些固定写法,记录下来,方便自己后续开发过程中直接拷贝粘贴。 1、div 设置宽度自适应文本内容:设置 div 节点的 class 为下面 auto-adjust-text-width .auto-adjust-text-width {display: inline-b…

做外贸网站建设肉部网站建设包括哪些

介绍 在我以前的文章中,我介绍了NONSTRICT_READ_WRITE二级缓存并发机制。 在本文中,我将使用READ_WRITE策略继续本主题。 直写式缓存 NONSTRICT_READ_WRITE是一种通读缓存策略,可更新最终无效的缓存条目。 尽管这种策略可能很简单&#xff0…

seo做的比较好的网站的几个特征夜月直播免费下载

ubuntu安装zsh、oh-my-zsh及常用配置 目前,ubuntu默认的shell是bash,但还有一种shell,叫做zsh它比bash更加强大,功能也更加完善,zsh虽说功能强大,但是配置比较复杂导致流行度不是很高 但是好东西终究是好…

主办单位性质与网站名称不符我在某网站网站做代理

400V功率放大器是一台非常实用的讯号放大器,它具有体积小、重量轻及操作方便,较高的电压输出(400Vp-p)以及较大的输出电流(2.8Ap-p)等优点。 HAP-4001连续输出直流电流量最大值达到0.5 A,电压放大增益40倍&#xff0c…

西宁网站制作哪里好北京网站关键字优化

一、ES数据基础类型 1、数据类型 字符串 主要包括: text和keyword两种类型,keyword代表精确值不会参与分词,text类型的字符串会参与分词处理 数值 包括: long, integer, short, byte, double, float 布尔值 boolean 时间 date 数组 数组类型不…

台州网站优化公司一级a做爰片迅雷网站

首先下载python地址: https://www.python.org/downloads/release/python-361/下载页面中有多个版本: web-based installer 是需要通过联网完成安装的 executable installer 是可执行文件(*.exe)方式安装 embeddable zip file 嵌入式版本,可…

食品 药品 监督 网站 源码 php全球设计师知识更新服务平台

一、引入 多关键字排序:假如现在有一个员工表。要求按照薪资排序,薪资相同的员工按照年龄排序。 先按照年龄进行排序,再按照薪资进行稳定的排序 按照这种思路我们对[32,13,94,52,17,54,93]排序: 先比较十位数的数字大小&#…