1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關(guān)咨詢
      選擇下列產(chǎn)品馬上在線溝通
      服務(wù)時間:8:30-17:00
      你可能遇到了下面的問題
      關(guān)閉右側(cè)工具欄

      新聞中心

      這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
      如何在Jest中使用Vue-test-utils-創(chuàng)新互聯(lián)

      本篇文章給大家分享的是有關(guān)如何在Jest中使用Vue-test-utils,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

      青白江網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)建站成立與2013年到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站

      介紹

      Vue-test-utils是Vue的官方的單元測試框架,它提供了一系列非常方便的工具,使我們更加輕松的為Vue構(gòu)建的應(yīng)用來編寫單元測試。主流的 JavaScript 測試運行器有很多,但 Vue Test Utils 都能夠支持。它是測試運行器無關(guān)的。

      Jest,是由Facebook開發(fā)的單元測試框架,也是Vue推薦的測試運行器之一。Vue對它的評價是:

      Jest 是功能最全的測試運行器。它所需的配置是最少的,默認安裝了 JSDOM,內(nèi)置斷言且命令行的用戶體驗非常好。不過你需要一個能夠?qū)挝募M件導(dǎo)入到測試中的預(yù)處理器。我們已經(jīng)創(chuàng)建了 vue-jest 預(yù)處理器來處理最常見的單文件組件特性,但仍不是 vue-loader 100% 的功能。

      我認為可以這樣理解,Vue-test-utils在Vue和Jest之前提供了一個橋梁,暴露出一些接口,讓我們更加方便的通過Jest為Vue應(yīng)用編寫單元測試。

      安裝

      通過Vue-cli創(chuàng)造模板腳手架時,可以選擇是否啟用單元測試,并且選擇單元測試框架,這樣Vue就幫助我們自動配置好了Jest。

      如果是后期添加單元測試的話,首先要安裝Jest和Vue Test Utils:

      npm install --save-dev jest @vue/test-utils

      然后在package.json中定義一個單元測試的腳本。

      // package.json
      {
       "scripts": {
        "test": "jest"
       }
      }

      為了告訴Jest如何處理*.vue文件,需要安裝和配置vue-jest預(yù)處理器:

      npm install --save-dev vue-jest

      接下來在jest.conf.js配置文件中進行配置:

      module.exports = {
       moduleFileExtensions: ['js', 'json', 'vue'],
       moduleNameMapper: {
        '^@/(.*)$': '/src/$1'
       },
       transform: {
        '^.+\\.js$': '/node_modules/babel-jest',
        '.*\\.(vue)$': '/node_modules/vue-jest'
       },
      }

      其他的具體的配置可以參考官方文檔。

      配置好了之后,就可以開始編寫單元測試了。

      import { mount } from '@vue/test-utils'
      import Component from './component'
      
      describe('Component', () => {
       test('是一個 Vue 實例', () => {
        const wrapper = mount(Component)
        expect(wrapper.isVueInstance()).toBeTruthy()
       })
      })

      上面的例子中,就是通過vue-test-utils提供的mount方法來掛載組件,創(chuàng)建包裹器和Vue實例

      如果不使用vue-test-utils也是可以掛載組件的:

      import Vue from 'vue';
      import Test1 from '@/components/Test1';
      
      const Constructor = Vue.extend(HelloWorld);
      const vm = new Constructor().$mount();

      啟用單元測試的命令:

      npm run unit

      可以在后面加上-- --watch啟動監(jiān)聽模式

      別名配置

      使用別名在Vue中很常見,可以讓我們避免使用復(fù)雜、易錯的相對路徑:

      import Page from '@/components/Test5/Test5'

      上面的@就是別名,在使用Vue-cli搭建的項目中,默認已經(jīng)在webpack.base.conf.js中對@進行了配置:

      module.exports = {
       ...
       resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
         'vue$': 'vue/dist/vue.esm.js',
         '@': path.join(__dirname, '..', 'src')
        }
       },
      }

      同樣,使用Jest時也需要在Jest的配置文件jest.conf.js中進行配置

      "jest": {
       "moduleNameMapper": {
        '^@/(.*)$': "/src/$1",
       },
      ...

      Shallow Rendering

      創(chuàng)建一個App.vue:

      
      
      

      然后創(chuàng)建一個Test1組件

      
      
      

      針對App.vue編寫單元測試文件App.spec.js

      // 從測試實用工具集中導(dǎo)入 `mount()` 方法
      import { mount } from 'vue-test-utils';
      // 導(dǎo)入你要測試的組件
      import App from '@/App';
      
      describe('App.test.js', () => {
       let wrapper,
        vm;
      
       beforeEach(() => {
        wrapper = mount(App);
        vm = wrapper.vm;
        wrapper.setProps({ messages: ['Cat'] })
       });
      
       it('equals messages to ["Cat"]', () => {
        expect(vm.messages).toEqual(['Cat'])
       });
      
       // 為App的單元測試增加快照(snapshot):
       it('has the expected html structure', () => {
        expect(vm.$el).toMatchSnapshot()
       })
      });

      執(zhí)行單元測試后,測試通過,然后Jest會在test/__snapshots__/文件夾下創(chuàng)建一個快照文件App.spec.js.snap

      // Jest Snapshot v1, https://goo.gl/fbAQLP
      
      exports[`App.test.js has the expected html structure 1`] = `
      
       
        

         Cat   

       
      `;

       

      通過快照我們可以發(fā)現(xiàn),子組件Test1被渲染到App中了。

      這里面有一個問題:單元測試應(yīng)該以獨立的單位進行。也就是說,當(dāng)我們測試App時,不需要也不應(yīng)該關(guān)注其子組件的情況。這樣才能保證單元測試的獨立性。比如,在created鉤子函數(shù)中進行的操作就會給測試帶來不確定的問題。

      為了解決這個問題,Vue-test-utils提供了shallow方法,它和mount一樣,創(chuàng)建一個包含被掛載和渲染的Vue組件的Wrapper,不同的創(chuàng)建的是被存根的子組件。

      這個方法可以保證你關(guān)心的組件在渲染時沒有同時將其子組件渲染,避免了子組件可能帶來的副作用(比如Http請求等)

      所以,將App.spec.js中的mount方法更改為shallow方法,再次查看快照

      // Jest Snapshot v1, https://goo.gl/fbAQLP
      
      exports[`App.test.js has the expected html structure 1`] = `
      
       
      `;

      可以看出來,子組件沒有被渲染,這時候針對App.vue的單元測試就從組件樹中被完全隔離了。??

      測試DOM結(jié)構(gòu)

      通過mount、shallow、find、findAll方法都可以返回一個包裹器對象,包裹器會暴露很多封裝、遍歷和查詢其內(nèi)部的Vue組件實例的便捷的方法。

      其中,find和findAll方法都可以都接受一個選擇器作為參數(shù),find方法返回匹配選擇器的DOM節(jié)點或Vue組件的Wrapper,findAll方法返回所有匹配選擇器的DOM節(jié)點或Vue組件的Wrappers的WrapperArray。

      一個選擇器可以是一個CSS選擇器、一個Vue組件或是一個查找選項對象。

      CSS選擇器:可以匹配任何有效的CSS選擇器

      • 標簽選擇器 (div、foo、bar)

      • 類選擇器 (.foo、.bar)

      • 特性選擇器 ([foo]、[foo="bar"])

      • id 選擇器 (#foo、#bar)

      • 偽選擇器 (div:first-of-type)

      • 符合選擇器(div > #bar > .foo、div + .foo)

      Vue組件:Vue 組件也是有效的選擇器。

      查找選項對象:

      • Name:可以根據(jù)一個組件的name選擇元素。wrapper.find({ name: 'my-button' })

      • Ref:可以根據(jù)$ref選擇元素。wrapper.find({ ref: 'myButton' })

      這樣我們就可以對DOM的結(jié)構(gòu)進行驗證:

      describe('Test for Test1 Component', () => {
       let wrapper,
        vm;
      
       beforeEach(() => {
        // wrapper = mount(App);
        wrapper = shallow(Test1, {
         propsData: {
          messages: ['bye']
         }
        });
       });
      
       it('is a Test1 component', () => {
        // 使用Vue組件選擇器
        expect(wrapper.is(Test1)).toBe(true);
        // 使用CSS選擇器
        expect(wrapper.is('.outer')).toBe(true);
        // 使用CSS選擇器
        expect(wrapper.contains('p')).toBe(true)
       });
      });

      還可以進行一步對DOM結(jié)構(gòu)進行更細致的驗證:

      // exists():斷言 Wrapper 或 WrapperArray 是否存在。
      it('不存在img', () = > {
       expect(wrapper.findAll('img').exists()).toBeFalsy()
      });
      
      // isEmpty():斷言 Wrapper 并不包含子節(jié)點。
      it('MyButton組件不為空', () = > {
       expect(wrapper.find(MyButton).isEmpty()).toBeFalsy()
      });
      
      // attributes():返回 Wrapper DOM 節(jié)點的特性對象
      // classes():返回 Wrapper DOM 節(jié)點的 class 組成的數(shù)組
      it('MyButton組件有my-class類', () = > {
       expect(wrapper.find(MyButton).attributes().class).toContain('my-button');
       expect(wrapper.find(MyButton).classes()).toContain('my-button');
      })

      測試樣式

      UI的樣式測試為了測試我們的樣式是否復(fù)合設(shè)計稿預(yù)期。同時通過樣式測試我們可以感受當(dāng)我們code變化帶來的UI變化,以及是否符合預(yù)期。

      inline style :如果樣式是inline style,可以使用hasStyle來驗證,也可以使用Jest的Snapshot Testing最方便。

      // hasStyle:判斷是否有對應(yīng)的內(nèi)聯(lián)樣式
      it('MyButton組件有my-class類', () = > {
        expect(wrapper.find(MyButton).hasStyle('padding-top', '10')).toBeTruthy()
      })

      CSS:屬于E2E測試,把整個系統(tǒng)當(dāng)作一個黑盒,只有UI會暴露給用戶用來測試一個應(yīng)用從頭到尾的流程是否和設(shè)計時候所想的一樣 。有專門的E2E測試框架。比較流行的E2E測試框架有nightwatch等,關(guān)于E2E測試框架的介紹可以參考這篇文章。

      測試Props

      父組件向子組件傳遞數(shù)據(jù)使用Props,而子組件向父組件傳遞數(shù)據(jù)則需要在子組件出發(fā)父組件的自定義事件

      當(dāng)測試對父組件向子組件傳遞數(shù)據(jù)這一行為時,我們想要測試的當(dāng)我們傳遞給子組件一個特定的參數(shù),子組件是否會按照我們所斷言的那樣變現(xiàn)。

      在初始化時向子組件傳值,使用的方法是propsData。

      const wrapper = mount(Foo, {
       propsData: {
        foo: 'bar'
       }
      })

      也可以使用setProps方法:

      const wrapper = mount(Foo)
      wrapper.setProps({ foo: 'bar' })

      我們傳遞給Test1組件的messages一個['bye']數(shù)組,來驗證是否存在:

      beforeEach(() = > {
       wrapper = mount(Test1, {
        propsData: {
         messages: ['bye']
        }
       });
      });
      
      // props:返回 Wrapper vm 的 props 對象。
      it('接收到了bye作為Props', () = > {
       expect(wrapper.props().messages).toContain('bye')
      });

      有時候會對Props的Type、默認值或者通過validator對Prop進行自定義的驗證

      props: {
       messages: {
        type: Array,
        required: true,
        validator: (messages) = > messages.length > 1,
        default () {
         return [0, 2]
        }
       }
      },

      通過Vue實例的$options獲取包括Props在內(nèi)的初始化選項:

      // vm.$options返回Vue實例的初始化選項
      describe('驗證Props的各個屬性', () = > {
       wrapper = mount(Test1, {
        propsData: {
         messages: ['bye', 'bye', 'bye']
        }
       });
       const messages = wrapper.vm.$options.props.messages;
       it('messages is of type array', () = > {
        expect(messages.type).toBe(Array)
       });
       it('messages is required', () = > {
        expect(messages.required).toBeTruthy()
       });
       it('messages has at least length 2', () = > {
        expect(messages.validator && messages.validator(['a'])).toBeFalsy();
        expect(messages.validator && messages.validator(['a', 'a'])).toBeTruthy();
       });
       wrapper.destroy()
      });

      測試自定義事件

      自定義事件要測試點至少有以下兩個:

      • 測試事件會被正常觸發(fā)

      • 測試事件被觸發(fā)后的后續(xù)行為符合預(yù)期

      具體到Test1組件和MyButton組件來看:

      TEST1組件:

      // TEST1
      
      
      // 省略一些代碼
      
      methods: {
       addCounter(value) {
        this.count = value
       }
      },

      MyButton組件:

      Click {{buttonValue}} {{innerCount}}、
      
      // 省略一些代碼
      
      data() {
       return {
        innerCount: 0
       }
      },
      computed: {},
      methods: {
       increment() {
        this.innerCount += 1;
        this.$emit('add', this.innerCount)
       }
      },

      要測試的目的是:

      1. 當(dāng)MyButton組件的按鈕被點擊后會觸發(fā)increment事件
      2. 點擊事件發(fā)生后,Test1組件的addCounter函數(shù)會被觸發(fā)并且結(jié)果符合預(yù)期(及數(shù)字遞增)

      首先為MyButton編寫單元測試文件:

      describe('Test for MyButton Component', () => {
       const wrapper = mount(MyButton);
      
       it('calls increment when click on button', () => {
        // 創(chuàng)建mock函數(shù)
        const mockFn = jest.fn();
        // 設(shè)置 Wrapper vm 的方法并強制更新。
        wrapper.setMethods({
         increment: mockFn
        });
        // 觸發(fā)按鈕的點擊事件
        wrapper.find('button').trigger('click');
        expect(mockFn).toBeCalled();
        expect(mockFn).toHaveBeenCalledTimes(1)
       })
      });

      通過setMethods方法用mock函數(shù)代替真實的方法,然后就可以斷言點擊按鈕后對應(yīng)的方法有沒有被觸發(fā)、觸發(fā)幾次、傳入的參數(shù)等等。

      現(xiàn)在我們測試了點擊事件后能觸發(fā)對應(yīng)的方法,下面要測試的就是increment方法將觸發(fā)Test1組件中自定義的add方法

      // increment方法會觸發(fā)add方法
      it('triggers a addCounter event when a handleClick method is called', () = > {
       const wrapper = mount(MyButton);
      
       // mock自定義事件
       const mockFn1 = jest.fn();
       wrapper.vm.$on('add', mockFn1);
      
       // 觸發(fā)按鈕的點擊事件
       wrapper.find('button').trigger('click');
       expect(mockFn1).toBeCalled();
       expect(mockFn1).toHaveBeenCalledWith(1);
      
       // 再次觸發(fā)按鈕的點擊事件
       wrapper.find('button').trigger('click');
       expect(mockFn1).toHaveBeenCalledTimes(2);
       expect(mockFn1).toHaveBeenCalledWith(2);
      })

      這里使用了$on方法,將Test1自定義的add事件替換為Mock函數(shù)

      對于自定義事件,不能使用trigger方法觸發(fā),因為trigger只是用DOM事件。自定義事件使用$emit觸發(fā),前提是通過find找到MyButton組件

      // $emit 觸發(fā)自定義事件
      describe('驗證addCounter是否被觸發(fā)', () = > {
       wrapper = mount(Test1);
       it('addCounter Fn should be called', () = > {
        const mockFn = jest.fn();
        wrapper.setMethods({
         'addCounter': mockFn
        });
        wrapper.find(MyButton).vm.$emit('add', 100);
        expect(mockFn).toHaveBeenCalledTimes(1);
       });
       wrapper.destroy()
      });

      測試計算屬性

      創(chuàng)建Test2組件,實現(xiàn)功能是使用計算屬性將輸入框輸入的字符翻轉(zhuǎn):

      
      
      
      
      
       .wrapper {
        width: 300px;
        margin: 0 auto;
        text-align: left;
       }
      

      在Test2.spec.js中,可以通過wrapper.vm屬性訪問一個實例所有的方法和屬性。這只存在于 Vue 組件包裹器中。

      describe('Test for Test2 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        wrapper = shallow(Test2);
       });
      
       afterEach(() => {
        wrapper.destroy()
       });
      
       it('returns the string in normal order if reversed property is not true', () => {
        wrapper.setProps({needReverse: false});
        wrapper.vm.inputValue = 'ok';
        expect(wrapper.vm.outputValue).toBe('ok')
       });
      
       it('returns the string in normal order if reversed property is not provided', () => {
        wrapper.vm.inputValue = 'ok';
        expect(wrapper.vm.outputValue).toBe('ok')
       });
      
       it('returns the string in reversed order if reversed property is true', () => {
        wrapper.setProps({needReverse: true});
        wrapper.vm.inputValue = 'ok';
        expect(wrapper.vm.outputValue).toBe('ko')
       })
      
      });

      測試監(jiān)聽器

      Vue提供的watch選項提供了一個更通用的方法,來響應(yīng)數(shù)據(jù)的變化。

      為Test添加偵聽器:

      watch: {
       inputValue: function(newValue, oldValue) {
        if (newValue.trim().length > 0 && newValue !== oldValue) {
         this.printNewValue(newValue)
        }
       }
      },
      methods: {
       printNewValue(value) {
        console.log(value)
       }
      },

      為了測試,首先開始測試前將console的log方法用jest的spyOn方法mock掉,最好在測試結(jié)束后通過mockClear方法將其重置,避免無關(guān)狀態(tài)的引入。

      describe('Test watch', () = > {
        let spy;
        beforeEach(() = > {
         wrapper = shallow(Test2);
         spy = jest.spyOn(console, 'log')
        });
        afterEach(() = > {
         wrapper.destroy();
         spy.mockClear()
        });
      }

      然后執(zhí)行給inputValue賦值,按照預(yù)期,spy方法會被調(diào)用

      it('is called with the new value in other cases', () = > {
       wrapper.vm.inputValue = 'ok';
       expect(spy).toBeCalled()
      });

      但是在執(zhí)行之后我們發(fā)現(xiàn)并非如此,spy并未被調(diào)用,原因是:

      watch中的方法被Vue**推遲**到了更新的下一個循環(huán)隊列中去異步執(zhí)行,如果這個watch被觸發(fā)多次,只會被推送到隊列一次。這種緩沖行為可以有效的去掉重復(fù)數(shù)據(jù)造成的不必要的性能開銷。

      所以當(dāng)我們設(shè)置了inputValue為'ok'之后,watch中的方法并沒有立刻執(zhí)行,但是expect卻執(zhí)行了,所以斷言失敗了。

      解決方法就是將斷言放到$nextTick中,在下一個循環(huán)隊列中執(zhí)行,同時在expect后面執(zhí)行Jest提供的done()方法,Jest會等到done()方法被執(zhí)行才會結(jié)束測試。

      it('is called with the new value in other cases', (done) = > {
       wrapper.vm.inputValue = 'ok';
       wrapper.vm.$nextTick(() = > {
        expect(spy).toBeCalled();
        done()
       })
      });

      在測試第二個情況時,由于對inputValue賦值時spy會被執(zhí)行一次,所以需要清除spy的狀態(tài),這樣才能得出正確的預(yù)期:

      it('is not called with same value', (done) = > {
       wrapper.vm.inputValue = 'ok';
       wrapper.vm.$nextTick(() = > {
        // 清除已發(fā)生的狀態(tài)
        spy.mockClear();
        wrapper.vm.inputValue = 'ok';
        wrapper.vm.$nextTick(() = > {
         expect(spy).not.toBeCalled();
         done()
        })
       })
      });

      測試方法

      單元測試的核心之一就是測試方法的行為是否符合預(yù)期,在測試時要避免一切的依賴,將所有的依賴都mock掉。

      創(chuàng)建Test3組件,輸入問題后,點擊按鈕后,使用axios發(fā)送HTTP請求,獲取答案

      
      
      
      
      
       .wrapper {
        width: 500px;
        margin: 0 auto;
        text-align: left;
       }
      

       這個例子里面,我們僅僅關(guān)注測試getAnswer方法,其他的忽略掉。為了測試這個方法,我們需要做的有:

      • 我們不需要實際調(diào)用axios.get方法,需要將它mock掉

      • 我們需要測試是否調(diào)用了axios方法(但是并不實際觸發(fā))并且返回了一個Promise對象

      • 返回的Promise對象執(zhí)行了回調(diào)函數(shù),設(shè)置用戶名和頭像

      我們現(xiàn)在要做的就是mock掉外部依賴。Jest提供了一個很好的mock系統(tǒng),讓我們能夠很輕易的mock所有依賴,前面我們用過jest.spyOn方法和jest.fn方法,但對于上面的例子來說,僅使用這兩個方法是不夠的。

      我們現(xiàn)在要mock掉整個axios模塊,使用的方法是jest.mock,就可以mock掉依賴的模塊。

      jest.mock('dependency-path', implementationFunction)

      在Test3.spec.js中,首先將axios中的get方法替換為我們的mock函數(shù),然后引入相應(yīng)的模塊

      jest.mock('axios', () => ({
       get: jest.fn()
      }));
      import { shallow } from 'vue-test-utils';
      import Test3 from '@/components/Test3';
      import axios from 'axios';

      然后測試點擊按鈕后,axios的get方法是否被調(diào)用:

      describe('Test for Test3 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        axios.get.mockClear();
        wrapper = shallow(Test3);
       });
      
       afterEach(() = > {
        wrapper.destroy()
       });
      
       // 點擊按鈕后調(diào)用了 getAnswer 方法
       it('getAnswer Fn should be called', () => {
        const mockFn = jest.fn();
        wrapper.setMethods({getAnswer: mockFn});
        wrapper.find('button').trigger('click');
        expect(mockFn).toBeCalled();
       });
      
       // 點擊按鈕后調(diào)用了axios.get方法
       it('axios.get Fn should be called', () => {
        const URL = 'https://yesno.wtf/api';
        wrapper.find('button').trigger('click');
        expect(axios.get).toBeCalledWith(URL)
       });
      });

      測試結(jié)果發(fā)現(xiàn),雖然我們的mock函數(shù)被調(diào)用了,但是控制臺還是報錯了,原因是我們mock的axios.get方法雖然被調(diào)用了,但是并沒有返回任何值,所以報錯了,所以下一步我們要給get方法返回一個Promise,查看方法能否正確處理我們返回的數(shù)據(jù)

      jest.fn()接受一個工廠函數(shù)作為參數(shù),這樣就可以定義其返回值

      const mockData = {
       data: {
        answer: 'mock_yes',
        image: 'mock.png'
       }
      };
      jest.mock('axios', () => ({
       get: jest.fn(() => Promise.resolve(mockData))
      }));

      getAnswer是一個異步請求,Jest提供的解決異步代碼測試的方法有以下三種:

      1. 回調(diào)函數(shù)中使用done()參數(shù)

      2. Pomise

      3. Aysnc/Await

      第一種是使用在異步請求的回調(diào)函數(shù)中使用Jest提供的叫做done的單參數(shù),Jest會等到done()執(zhí)行結(jié)束后才會結(jié)束測試。

      我們使用第二種和第三種方法來測試getAnswer方法的返回值,前提就是在方法中返回一個Promise。(一般來說,在被測試的方法中給出一個返回值會讓測試更加容易)。 Jest會等待Promise解析完成。 如果承諾被拒絕,則測試將自動失敗。

      // axios.get方法返回值(Promise)
      it('Calls get promise result', () = > {
       return expect(wrapper.vm.getAnswer()).resolves.toEqual(mockData);
      });

      或者可以使用第三種方法,也就是使用async和await來測試異步代碼:

      // 可以用 Async/Await 測試 axios.get 方法返回值
      it('Calls get promise result 3', async() = > {
       const result = await wrapper.vm.getAnswer();
       expect(result).toEqual(mockData)
      });

      Jest都提供了resolves和rejects方法作為then和catch的語法糖:

      it('Calls get promise result 2', () = > {
       return wrapper.vm.getAnswer().then(result = > {
        expect(result).toEqual(mockData);
       })
      });
      
      it('Calls get promise result 4', async() = > {
       await expect(wrapper.vm.getAnswer()).resolves.toEqual(mockData)
      });

      mock依賴

      我們可以創(chuàng)建一個__mocks__文件夾,將mock文件放入其中,這樣就不必在每個測試文件中去單獨的手動mock模塊的依賴

      在__mocks__文件夾下創(chuàng)建axios.js文件:

      // test/__mocks__/axios.js
      const mock = {
       get: jest.fn(() => Promise.resolve({
        data: {
         answer: 'mock_yes',
         image: 'mock.png'
        }
       }))
      };
      export default mock

      這樣就可以將Test3.spec.js中的jest.mock部分代碼移除了。Jest會自動在__mocks__文件夾下尋找mock的模塊,但是有一點要注意,模塊的注冊和狀態(tài)會一直被保存,所有如果我們在Test3.spec.js最后增加一條斷言:

      // 如果不清除模塊狀態(tài)此條斷言會失敗
      it('Axios should not be called here', () = > {
       expect(axios.get).not.toBeCalled()
      });

      因為我們在beforeEach中添加了axios.get的狀態(tài)清除的語句 axios.get.mockClear(),所以上面的斷言會通過,否則會失敗。

      也可以用另外resetModules和clearAllMocks來確保每次開始前都重置模塊和mock依賴的狀態(tài)。

      beforeEach(() = > {
       wrapper = shallow(Test3);
       jest.resetModules();
       jest.clearAllMocks();
      });

      我們在項目中有時候會根據(jù)需要對不同的Http請求的數(shù)據(jù)進行Mock,以MockJS為例,一般每個組件(模塊)都有對應(yīng)的mock文件,然后通過index.js導(dǎo)入到系統(tǒng)。Jest也可以直接將MockJS的數(shù)據(jù)導(dǎo)入,只需要在setup.js中導(dǎo)入MockJS的index.js文件即可

      測試插槽

      插槽(slots)用來在組件中插入、分發(fā)內(nèi)容。創(chuàng)建一個使用slots的組件Test4

      // TEST4
      
        
      
      
      // MessageList
      
       
      
      
      // Message
      
    2. {{message}}
    3. 在測試slots時,我們的關(guān)注點是slots中的內(nèi)容是否在組件中出現(xiàn)在該出現(xiàn)的位置,測試方法和前面介紹的測試DOM結(jié)構(gòu)的方法相同。

      具體到例子中來看,我們要測試的是:Message組件是否出現(xiàn)在具有l(wèi)ist-messages的類的ul中。在測試時,為了將slots傳遞給MessageList組件,我們在MessageList.spec.js中的mount或者shallow方法中使用slots屬性

      import { mount } from 'vue-test-utils';
      import MessageList from '@/components/Test4/MessageList';
      
      describe('Test for MessageList of Test4 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        wrapper = mount(MessageList, {
         slots: {
          default: '
      '    }   });  });  afterEach(() => {   wrapper.destroy()  });  // 組件中應(yīng)該通過slots插入了div.fake-msg  it('Messages are inserted in a ul.list-messages element', () => {   const list = wrapper.find('ul.list-messages');   expect(list.contains('div.fake-msg')).toBeTruthy()  }) });

      為了測試內(nèi)容是否通過插槽插入了組件,所以我們偽造了一個div.fake-msg通過slots選項傳入MessageList組件,斷言組件中應(yīng)該存在這個div

      不僅如此,slots選項還可以傳入組件或者數(shù)組:

      import AnyComponent from 'anycomponent'
      
      mount(MessageList, {
       slots: {
        default: AnyComponent // or [AnyComponent, AnyComponent]
       }
      })

      這里面有一個問題,例如我們想測試Message組件是否通過插槽插入了MessageList組件中,我們可以將slots選項中傳入Message組件,但是由于Message組件需要傳入message作為Props,所以按照上面的說明,我們應(yīng)該這樣做:

      beforeEach(() = > {
       const fakeMessage = mount(Message, {
        propsData: {
         message: 'test'
        }
       });
       wrapper = mount(MessageList, {
        slots: {
         default: fakeMessage
        }
       })
      });

      對應(yīng)的斷言是:

      // 組件中應(yīng)該通過slots插入了Message,并且傳入的文本是test
      it('Messages are inserted in a ul.list-messages element', () = > {
       const list = wrapper.find('ul.list-messages');
       expect(list.contains('li')).toBeTruthy();
       expect(list.find('li').text()).toBe('test')
      })

      但是這會失敗,查了資料,貌似不能通過這種方式mounted的組件傳入slots中。

      雖然如此,我們可以而通過渲染函數(shù)(render function)來作為一種非正式的解決方法:

      const fakeMessage = {
       render(h) {
        return h(Message, {
         props: {
          message: 'test'
         }
        })
       }
      };
      wrapper = mount(MessageList, {
       slots: {
        default: fakeMessage
       }
      })

      測試命名插槽(Named Slots)

      測試命名插槽和默認插槽原理相同,創(chuàng)建Test5組件,里面應(yīng)用新的MessageList組件,組件中增加一個給定名字為header的插槽,并設(shè)定默認內(nèi)容:

          This is a default header         

      在Test5中就可以使用這個命名插槽:

      
       Awesome header
       
      

      對MessageList組件進行測試時,首先測試組件中是否渲染了命名插槽的默認內(nèi)容:

      // 渲染命名插槽的默認內(nèi)容
      it('Header slot renders a default header text', () = > {
       const header = wrapper.find('.list-header');
       expect(header.text()).toBe('This is a default header')
      });

      然后測試插槽是否能插入我們給定的內(nèi)容,只需要將mount方法中的slots選項的鍵值default改為被測試的插槽的name即可:

      // 向header插槽中插入內(nèi)容
      it('Header slot is rendered withing .list-header', () = > {
       wrapper = mount(MessageList, {
        slots: {
         header: '
      What an awesome header
      '   }  });  const header = wrapper.find('.list-header');  expect(header.text()).toBe('What an awesome header') })

      測試debounce

      我們經(jīng)常使用lodash的debounce方法,來避免一些高頻操作導(dǎo)致的函數(shù)在短時間內(nèi)被反復(fù)執(zhí)行,比如在Test6組件中,對button的點擊事件進行了debounce,頻率為500ms,這就意味著如果在500ms內(nèi)如果用戶再次點擊按鈕,handler方法會被推遲執(zhí)行:

      
      
      

      在編寫Test6的單元測試時,我們有一個這樣的預(yù)期:當(dāng)addCounter方法被觸發(fā)時,500ms內(nèi)沒有任何后續(xù)操作,handler方法會被觸發(fā)

      如果沒有進行特殊的處理,單元測試文件應(yīng)該是這樣的:

      import { shallow } from 'vue-test-utils';
      import Test6 from '@/components/Test6';
      
      describe('Test for Test6 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        wrapper = shallow(Test6);
       });
      
       afterEach(() => {
        wrapper.destroy()
       });
      
       it('test for lodash', () => {
        const mockFn2 = jest.fn();
        wrapper.setMethods({ handler: mockFn2 });
        wrapper.vm.addCounter();
        expect(mockFn2).toHaveBeenCalledTimes(1);
       })
      });

      測試結(jié)果發(fā)現(xiàn),addCounter被觸發(fā)時handler方法并沒有執(zhí)行

      因為lodash中debounce方法涉及到了setTimeout,`hanlder方法應(yīng)該是在500ms后執(zhí)行,所以在此時執(zhí)行時方法沒有執(zhí)行。

      所以我們需要在Jest中對setTimeout進行特殊的處理:Jest提供了相關(guān)的方法,我們需要使用的是jest.useFakeTimers()和jest.runAllTimers()

      前者是用來讓Jest模擬我們用到的諸如setTimeout、setInterval等計時器,而后者是執(zhí)行setTimeout、setInterval等異步任務(wù)中的宏任務(wù)(macro-task)并且將需要的新的macro-task放入隊列中并執(zhí)行,更多信息的可以參考官網(wǎng)的timer-mocks。

      所以對test6.spec.js進行修改,在代碼開始增加jest.useFakeTimers(),在觸發(fā)addCounter方法后通過jest.runAllTimers()觸發(fā)macor-task任務(wù)

      jest.useFakeTimers();
      
      import { shallow } from 'vue-test-utils';
      import Test6 from '@/components/Test6';
      import _ from 'lodash';
      
      describe('Test for Test6 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        wrapper = shallow(Test6);
       });
      
       afterEach(() => {
        wrapper.destroy()
       });
      
       it('test for lodash', () => {
        const mockFn2 = jest.fn();
        wrapper.setMethods({ handler: mockFn2 });
        wrapper.vm.addCounter();
      
        jest.runAllTimers();
      
        expect(mockFn2).toHaveBeenCalledTimes(1);
       })
      });

      結(jié)果還是失敗,報錯原因是:

      Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out…

      程序陷入了死循環(huán),換用Jest提供額另外一個API:jest.runOnlyPendingTimers(),這個方法只會執(zhí)行當(dāng)前隊列中的macro-task,遇到的新的macro-task則不會被執(zhí)行

      將jest.runAllTimers()替換為jest.runOnlyPendingTimers()后,上面的錯誤消失了,但是handler仍然沒有被執(zhí)行

      在查了許多資料后,這可能是lodash的debounce機制與jest的timer-mocks 無法兼容,如果有人能夠解決這個問題希望能夠指教。

      這樣的情況下,我們退而求其次,我們不去驗證addCounter是否會被debounce,因為debounce是第三方模塊的方法,我們默認認為是正確的,我們要驗證的是addCounter能夠正確觸發(fā)handler方法即可。

      所以我們可以另辟蹊徑,通過mock將lodash的debounce修改為立即執(zhí)行的函數(shù),我們要做的是為lodash的debounce替換為jest.fn(),并且提供一個工廠函數(shù),返回值就是傳入的函數(shù)

      import _ from 'lodash';
      
      jest.mock('lodash', () => ({
       debounce: jest.fn((fn => fn))
      }));

      在如此修改后,測試通過,handler方法正確執(zhí)行

      同一個方法的多次mock

      在一個組件中,我們可能會多次用到同一個外部的方法,但是每次返回值是不同的,我們可能要對它進行多次不同的mock

      舉個例子,在組件Test7中,mounted的時候forData返回一個數(shù)組,經(jīng)過map處理后賦給text,點擊getResult按鈕,返回一個0或1的數(shù)字,根據(jù)返回值為result賦值

      
      
      

      針對getResult方法編寫單元測試,針對兩種返回值編寫了兩個用例,在用例中將forData方法mock掉,返回值是一個Promise值,再根據(jù)給定的返回值,判斷結(jié)果是否符合預(yù)期:

      describe('Test for Test7 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        wrapper = shallow(Test7);
       });
      
       afterEach(() => {
        wrapper.destroy()
       });
      
       it('test for getResult', async () => {
        // 設(shè)定forData返回值
        const mockResult = 0;
        const mockFn = jest.fn(() => (Promise.resolve(mockResult)));
        helper.forData = mockFn;
      
        // 執(zhí)行
        await wrapper.vm.getResult();
        // 斷言
        expect(mockFn).toHaveBeenCalledTimes(1);
        expect(wrapper.vm.result).toBe('000')
       });
      
       it('test for getResult', async () => {
        // 設(shè)定forData返回值
        const mockResult = 1;
        const mockFn = jest.fn(() => (Promise.resolve(mockResult)));
        helper.forData = mockFn;
      
        // 執(zhí)行
        await wrapper.vm.getResult();
        // 斷言
        expect(mockFn).toHaveBeenCalledTimes(1);
        expect(wrapper.vm.result).toBe('111')
       })
      });

      運行測試用例,雖然測試用例全部通過,但是控制臺仍然報錯了:

      (node:17068) UnhandledPromiseRejectionWarning: TypeError: ret.map is
      not a function

      為什么呢?

      原因就是在于,在第一個用例運行之后,代碼中的forData方法被我們mock掉了,所以在運行第二個用例的時候,執(zhí)行mounted的鉤子函數(shù)時,forData返回值就是我們在上個用例中給定的1,所以使用map方法會報錯

      為了解決這個問題,我們需要在beforeEach(或afterEach)中,重置forData的狀態(tài),如果在代碼中使用了MockJS的情況下,我們只需要讓默認的forData獲取的數(shù)據(jù)走原來的路徑,由MockJS提供假數(shù)據(jù)即可,這樣我們只需要在一代碼的最開始將forData保存,在beforeEach使用restoreAllMocks方法重置狀態(tài),然后在恢復(fù)forData狀態(tài),然后每個用例中針對forData進行單獨的mock即可

      const test = helper.forData;
      
      describe('Test for Test7 Component', () => {
       let wrapper;
      
       beforeEach(() => {
        jest.restoreAllMocks();
        helper.forData = test;
        wrapper = shallow(Test7);
       });
      
       afterEach(() => {
        wrapper.destroy()
       });
      
       // 用例不變

      如果沒有使用MockJS,那么都需要我們提供數(shù)據(jù),就需要在afterEach中提供mounted時需要的數(shù)據(jù):

      beforeEach(() = > {
       jest.restoreAllMocks();
       const mockResult = [{ name: 1}, {name: 2}];
       helper.forData = jest.fn(() = > (Promise.resolve(mockResult)));
       wrapper = shallow(Test7);
      });

      這樣處理過后,運行用例通過,并且控制臺也不會報錯了。

      如果是在同一個方法中遇到了需要不同返回結(jié)果的forData,比如下面的getQuestion方法:

      async getQuestion() {
       const r1 = await forData(axios.get('result1.do'));
       const r2 = await forData(axios.get('result2.do'));
       const res = r1 + r2;
       switch (res) {
        case 2:
         {
          this.result = '222';
          break
         }
        case 3:
         {
          this.result = '333';
          break
         }
       }
      },

      通過forData發(fā)出了兩個不同的HTTP請求,返回結(jié)果不同,這時我們在測試時就需要使用mockImplementationOnce方法,這個方法mock的函數(shù)只被調(diào)用一次,多次調(diào)用時就會根據(jù)定義時的順序依次調(diào)用mock函數(shù),所以測試用例如下:

      it('test for getQuestion', async() = > {
       // 設(shè)定forData返回值
       const mockFn = jest.fn()
        .mockImplementationOnce(() = > (Promise.resolve(1)))
        .mockImplementationOnce(() = > (Promise.resolve(2)));
       helper.forData = mockFn;
       // 執(zhí)行
       await wrapper.vm.getQuestion();
       // 斷言
       expect(mockFn).toHaveBeenCalledTimes(2);
       expect(wrapper.vm.result).toBe('333')
      });

      以上就是如何在Jest中使用Vue-test-utils,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道。

      另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。


      網(wǎng)站名稱:如何在Jest中使用Vue-test-utils-創(chuàng)新互聯(lián)
      URL分享:http://www.ef60e0e.cn/article/decgoh.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        吴旗县| 九龙县| 香港 | 山东| 甘孜县| 监利县| 绥棱县| 开平市| 托里县| 东海县| 桦南县| 松滋市| 武强县| 壶关县| 红桥区| 泸西县| 揭西县| 青州市| 青田县| 崇礼县| 滦平县| 遂昌县| 米易县| 阿尔山市| 香河县| 凤台县| 麻城市| 成武县| 洪雅县| 开阳县| 资中县| 洪湖市| 荆门市| 太白县| 宁津县| 化德县| 安康市| 望江县| 安徽省| 黄浦区| 高平市|