jest+vue-test-utils初步實踐

1、起步html

1. jestvue

Jest是 Facebook 的一套開源的 JavaScript 測試框架, 它自動集成了斷言、JSDom、覆蓋率報告等開發者所須要的全部測試工具,配置較少,對vue框架友好。node

2. vue-test-utilswebpack

Vue Test Utils 是 Vue.js 官方的單元測試實用工具庫,爲jest和vue提供了一個橋樑,暴露出一些接口,讓咱們更加方便的經過Jest爲Vue應用編寫單元測試。web

3. 安裝vuex

若是已經安裝配置了webpack、babel的vue腳手架,如今須要在安裝的是:npm

npm i jest @vue/test-utils vue-jest babel-jest jest-serializer-vue -D

4. 在package.json中定義單元測試的腳本和配置jestjson

"scripts": {
    "test": "jest"
  },
 "jest": {
    "moduleFileExtensions": [
      "js",
      // 告訴 Jest 處理 `*.vue` 文件
      "vue"
    ],
    "moduleNameMapper": {
      // 支持源代碼中相同的 `@` -> `src` 別名
      "^@/(.*)$": "<rootDir>/src/$1"
    },
    "transform": {
      // 用 `babel-jest` 處理 `*.js` 文件
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      // 用 `vue-jest` 處理 `*.vue` 文件
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
    },
    "snapshotSerializers": [
      // 快照的序列化工具
      "<rootDir>/node_modules/jest-serializer-vue"
    ],
    //成多種格式的測試覆蓋率報告  可選
    "collectCoverage": true,
    "collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"]
  }

5. 配置.babelrc文件數組

{
  "presets": [
    ["env", { "modules": false }]
  ],
  "env": {
    "test": {
      "presets": [
        ["env"]
      ]
    }
  }
}

6. 寫測試文件,放在根目錄下test文件夾中,以.spec.js或.test.js爲後綴名的文件,正常名字和組件名一致,如測試header組件,應該名字叫作header.spec.js或header.test.jspromise

header.vue

<template>
  <div>
    <div>
      <span class="count">{{ count }}</span>
      <button @click="increment">Increment</button>
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      count: 0
    }
  },
  methods: {
    increment () {
      this.count++
    }
  }
}
</script>

header.spec.js

import { mount } from '@vue/test-utils'
import header from '@/components/header.vue'

describe('header', () => {
  const wrapper = mount(header)

  it('點擊按鈕', () => {
    const button = wrapper.find('button');
    expect(button.text()).toBe('Increment');
    button.trigger('click');
    expect(wrapper.find('.count').text()).toBe('1')
  })
})

7. 執行命令行:npm test,運行單元測試

 

2、jest經常使用API

1. 全局函數

1)beforeAll(fn, timeout)/afterAll(fn, timeout)

在全部測試運行以前/以後執行,第二個參數可選,默認爲5秒

2)beforeEach(fn, timeout)/afterEach(fn, timeout)

在每一個測試運行以前/以後執行,第二個參數可選,默認爲5秒

3)describe(name, fn)

建立一個塊,將幾個相關的測試組合在一塊兒。

describe.only(name, fn)只運行一次

4)test(name, fn, timeout)

測試方法,test(name, fn, timeout) 等價於 it(name, fn, timeout), 第三個參數可選,默認爲5秒

test.only(name, fn, timeout),只運行一次

 

2. 匹配器

1)相等、不相等匹配

toBe

test('3+3等於6', () => {
  expect(3 + 3).toBe(6);
});

expect(3+3)返回咱們指望的結果,toBe是匹配器,匹配具體的某一個值

toEqual

若是是匹配對象,須要使用toEqual

    const obj = { one: 1, two: 2 };
    expect(obj).toBe({ one: 1, two: 2 });

not:匹配不相等

expect(3+3).not.toBe(6);

2)匹配真假

toBeNull 只匹配 null
toBeUndefined 只匹配 undefined
toBeDefined 與...相反 toBeUndefined
toBeTruthy匹配if聲明視爲真的任何內容
toBeFalsy匹配if語句視爲false的任何內容

3)匹配數字

toBeGreaterThan(3) 大於3
toBeGreaterThanOrEqual(3) 等於或大於3
toBeLessThan(3) 小於3
toBeLessThanOrEqual(3) 等於或小於3

對於浮點相等,請使用toBeCloseTo

    const value = 0.1 + 0.2;
    // expect(value).toBe(0.3); //報錯 Received: 0.30000000000000004
    expect(value).toBeCloseTo(0.3);

4)匹配字符串,toMatch 支持正則

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});

5)匹配數組,toContain,數組中是否包含

const shoppingList = [
  'paper towels',
  'beer',
];

test('the shopping list has beer on it', () => {
  expect(shoppingList).toContain('beer');
});

 

3. 異步函數

1)回調函數

回調是異步比較常見,實現以下

  function fetchData (callback) {
    setTimeout(() => {
      callback('peanut butter');
    }, 1000)
  }

  test('the data is peanut butter', done => {
    function callback (data) {
      expect(data).toBe('peanut butter');
      done();
    }
    fetchData(callback);
  });

若是不寫done(),將會報超時的錯誤

2)promise

function fetchData () {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('peanut butter')
      }, 1000)
    })
  }

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

assertions(1)表明的是在當前的測試中至少有一個斷言是被調用的,不然斷定爲失敗。

若是刪掉return語句,那麼你的測試將在fetchData完成以前結束。

配合 .resolves/.rejects使用

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

3)async/await

使用async/await可使代碼看起來更加整潔

  test('the data is peanut butter', async () => {
    expect.assertions(1);
    await expect(fetchData()).resolves.toBe('peanut butter');
  });

 

4. mock

在項目中,一個模塊的方法內經常會去調用另一個模塊的方法。在單元測試中,咱們可能並不須要關心內部調用的方法的執行過程和結果,只想知道它是否被正確調用便可,甚至會指定該函數的返回值。此時,使用Mock函數是十分有必要。

經常使用的方法有:jest.fn()/jest.mock()/jest.spyOn

1) jest.fn() 

jest.fn()是建立Mock函數最簡單的方式,若是沒有定義函數內部的實現,jest.fn()會返回undefined做爲返回值。                     

test('測試jest.fn()調用', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);

  // mockFn被調用
  expect(mockFn).toBeCalled();
  // mockFn被調用了一次
  expect(mockFn).toBeCalledTimes(1);
  // mockFn傳入的參數爲1, 2, 3
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})

test('jest.fn()添加返回值', () => {
  let mockFn = jest.fn().mockReturnValue('default');
  // mockFn執行後返回值爲default
  expect(mockFn()).toBe('default');
})

模擬另外一個模塊的調用狀況

fetch.js

export default {
  fetch (callback) {
    callback()
  }
}

fetch.test.js

import fetch from './fetch';

test('測試jest.fn()調用', () => {
  let mockFn = jest.fn();
  fetch.fetch(mockFn);
  expect(mockFn).toBeCalled()
})

2)jest.mock()

fetch.js文件夾中封裝的請求方法可能咱們在其餘模塊被調用的時候,並不須要進行實際的請求。這時使用jest.mock()去mock整個模塊是十分有必要的

建立event.js文件,用來調用fetch.js

import fetch from './fetch';
export
default { getPostList () { return fetch.fetch(data => { console.log('fetchPostsList be called!'); }); } }

fetch.test.js

import events from './event';
import fetch from './fetch';
jest.mock(
'./fetch.js'); test('mock 整個 fetch.js模塊', () => { expect.assertions(1); events.getPostList(); expect(fetch.fetch).toHaveBeenCalled(); });

使用jest.mock('./fetch.js')模擬fetch模塊,若不用jest.mock,會報錯:jest.fn() value must be a mock function or spy.

3)jest.sypOn()

在上面的jest.mock()中,並無執行console.log('fetchPostsList be called!'),說明fetch函數沒有執行。jest.spyOn()方法一樣建立一個mock函數,可是該mock函數不只可以捕獲函數的調用狀況,還能夠正常的執行被spy的函數。

import events from './event';
import fetch from './fetch';


test('使用jest.spyOn()監控fetch.fetch被正常調用', () => {
  expect.assertions(1);
  const spyFn = jest.spyOn(fetch, 'fetch');
  events.getPostList();
  expect(spyFn).toHaveBeenCalled();
})

使用jest.spyOn,執行了console.log('fetchPostsList be called!')。

 

 3、vue-test-utils經常使用API

1. mount

建立一個包含被掛載和渲染的 Vue 組件的 Wrapper

import { mount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
  it('renders a div', () => {
    const wrapper = mount(Foo)
    expect(wrapper.contains('div')).toBe(true)
  })
})

2.shallowMount

和 mount 同樣,建立一個包含被掛載和渲染的 Vue 組件的 Wrapper,不一樣的是被存根的子組件

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
  it('返回一個 div', () => {
    const wrapper = shallowMount(Foo)
    expect(wrapper.contains('div')).toBe(true)
  })
})

3.render render

在底層使用 vue-server-renderer 將一個組件渲染爲靜態的 HTML。

import { render } from '@vue/server-test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
  it('renders a div', async () => {
    const wrapper = await render(Foo)
    expect(wrapper.text()).toContain('<div></div>')
  })
})

4.createLocalVue 

createLocalVue 返回一個 Vue 的類供你添加組件、混入和安裝插件而不會污染全局的 Vue 類。
可經過 options.localVue 來使用

import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const localVue = createLocalVue()
const wrapper = shallowMount(Foo, {
  localVue,
  mocks: { foo: true }
})
expect(wrapper.vm.foo).toBe(true)

const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)

5.選擇器

不少方法的參數中都包含選擇器。一個選擇器能夠是一個 CSS 選擇器、一個 Vue 組件或是一個查找選項對象。

標籤選擇器 (div、foo、bar)
類選擇器 (.foo、.bar)
特性選擇器 ([foo]、[foo="bar"])
id 選擇器 (#foo、#bar)
僞選擇器 (div:first-of-type)
近鄰兄弟選擇器 (div + .foo)
通常兄弟選擇器 (div ~ .foo)

const buttonr = wrapper.find('.button')
const content = wrapper.find('#content')

6.僞造 $route 和 $router

有的時候你想要測試一個組件在配合 $route 和 $router 對象的參數時的行爲。這時候你能夠傳遞自定義假數據給 Vue 實例。

import { shallowMount } from '@vue/test-utils'

const $route = {
  path: '/home'
}

const wrapper = shallowMount(Component, {
  mocks: {
    $route
  }
})

expect(wrapper.vm.$route.path).toBe('/home')

7.測試vuex

在測試vuex,主要經過僞造state/getters/mutations/actions進行測試

.vue文件中

<template>
  <div>
    <h1>{{appName}}</h1>
    <h2>{{appNameWithVersion}}</h2>
    <button @click="updateAppName"
            class="mutation">mutation update</button>
    <button @click="updateActionAppName"
            class='action'>action update</button>
  </div>
</template>
<script>
export default {
  methods: {
    updateAppName () {
      this.$store.commit('setAppName', 'admin2')
    },
    updateActionAppName () {
      this.$store.dispatch('setName', 'admin3')
    }
  },
  computed: {
    appName () {
      return this.$store.state.appName;
    },
    appNameWithVersion () {
      return this.$store.getters.appNameWithVersion;
    }
  }
}
</script>

.spec.js文件中

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Header from '@/components/Header.vue'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('header.vue', () => {
  let wrapper;
  let store, state, getters, mutations, actions;

  beforeEach(() => {
    state = {
      appName: 'admin'
    };

    getters = {
      appNameWithVersion: () => 'getters'
    }

    mutations = {
      setAppName: jest.fn(),
    }

    actions = {
      setName: jest.fn(),
    }

    store = new Vuex.Store({
      state,
      getters,
      mutations,
      actions
    })

    wrapper = shallowMount(Header, {
      store,
      localVue
    })
  })

  it('測試vuex', () => {
    expect(wrapper.find('h1').text()).toBe(state.appName)
    expect(wrapper.find('h2').text()).toBe(getters.appNameWithVersion())
    wrapper.find('.mutation').trigger('click')
    expect(mutations.setAppName).toHaveBeenCalled()
    wrapper.find('.action').trigger('click')
    expect(actions.setName).toHaveBeenCalled()
  })
})

完...

 

 

參考:

1.https://vue-test-utils.vuejs.org/zh/

2.https://jestjs.io/docs/en/getting-started

3.http://www.cnblogs.com/Wolfmanlq/p/8018370.html

4.https://www.jianshu.com/p/70a4f026a0f1

5.https://www.jianshu.com/p/ad87eaf54622

相關文章
相關標籤/搜索