精华内容
下载资源
问答
  • 准备工作 注意:使用lerna开发工具库需要区分不同的包装,需要用到yarn工作区,所以推荐使用yarn // package.json文件设置,开启工作区 "private" : true , "workspaces" : [ "./packages/*" ...
  • yarn add @vue/test-utils vue-jest yarn jestjest-serializer-vue yarn add babel-jest babel-core@^7.0.0-bridge.0 :warning_selector::vue-jest依赖与babel-core。我们的环境现在都是babel7,通过npm安装的babel...
  • 开玩笑的匹配器测试实用程序 (jest-matcher-vue-test-utils) Cute matchers for Jest to test Vue components with Vue Test Utils. 对于可爱的匹配玩笑测试与Vue的成分VUE考试utils的 。 You can write tests for...

    开玩笑的匹配器测试实用程序 (jest-matcher-vue-test-utils)

    Cute matchers for Jest to test Vue components with Vue Test Utils.

    对于可爱的匹配玩笑测试与Vue的成分VUE考试utils的

    You can write tests for Vue component/store intuitively ⚡️

    您可以直观地为Vue组件/存储编写测试️

    it("Emits 'select' event by clicking PrimaryButton", () => {
      const wrapper = shallowMount(Component);
      
      expect(wrapper.emitted().select).toBeUndefined();
      wrapper.find(PrimaryButton).vm.$emit("click");
      expect(wrapper.emitted().select[0]).toBeTruthy();
    });

    becomes

    变成

    it("Emits 'select' event by clicking PrimaryButton", () => {
      const wrapper = shallowMount(Component);
      
      expect(() => {
        wrapper.find(PrimaryButton).vm.$emit("click");
      }).toEmit(wrapper, "select");
    });

    And all matchers have type definition and doc 💇‍♂️

    而且所有匹配器都有类型定义和文档💇‍♂️

    jest-matcher-vue-test-utils

    安装 (Installation)

    Get from npm:

    从npm获取:

    $ npm install -D jest-matcher-vue-test-utils

    Then, register matchers on your jest process:

    然后,在玩笑过程中注册匹配器:

    import vueTestUtilMatchers from "jest-matcher-vue-test-utils";
    expect.extend({ ...vueTestUtilMatchers });

    提供的匹配器 (Provided Matchers)

    包装上的存在 (Existence on Wrapper)

    toShow (toShow)

    Assert the function shows a content on Wrapper of vue-test-utils 断言该函数在vue-test-utils的包装上显示内容
    // error-message.vue
    <template>
      <div>
        <p v-if="isError" class="error">message</p>
      </div>
    </template>
    
    ...
    
    data: function () {
      return {
        isError: false
      }
    },
    methods: {
      showError () {
        this.isError = true;
      }
    }
    import Component from "./error-message.vue";
    
    it("show error by showError", async () => {
      return expect(async () => {
        wrapper.vm.showError();
        await wrapper.vm.$nextTick();
      }).toShow(wrapper, "p.error"); // Passes
    });

    toHide (toHide)

    Assert the function hides a content on Wrapper of vue-test-utils 断言该函数在vue-test-utils的包装器上隐藏内容
    // error-message.vue
    <template>
      <div>
        <p v-if="isError" class="error">message</p>
      </div>
    </template>
    
    ...
    
    data: function () {
      return {
        isError: true
      }
    },
    methods: {
      hideError () {
        this.isError = false;
      }
    }
    import Component from "./error-message.vue";
    
    it("show error by showError", async () => {
      return expect(async () => {
        wrapper.vm.hideError();
        await wrapper.vm.$nextTick();
      }).toHide(wrapper, "p.error"); // Passes
    });

    包装事件 (Events on Wrapper)

    toEmit (toEmit)

    Assert the action emits the event (with the payload optionally) on Wrapper of vue-test-utils 断言该动作在vue-test-utils的包装器上发出事件(可选地带有有效负载)
    // event.vue
    <template>
      <div @click="emitEvent('clicked')">
        Click Me
      </div>
    </template>
    
    <script>
    module.exports = {
      methods: {
        emitEvent (e) {
          this.$emit("special", e);
        }
      }
    }
    </script>
    import Component from "./event.vue";
    
    it("emits special event by click", () => {
      const wrapper = shallowMount(Component);
      expect(() => wrapper.trigger("click")).toEmit(wrapper, "special"); // Passes
      expect(() => wrapper.trigger("click")).toEmit(wrapper, "special", "clicked"); // Passes
    });

    Async function is supported as well.

    也支持异步功能。

    it("emits special event by click", async () => {
      const wrapper = shallowMount(Component);
      return expect(async () => triggersEventAsynchronously()).toEmit(wrapper, "special", "clicked"); // Passes
    });

    toHaveEmitted (toHaveEmitted)

    Assert the event is emitted (with the payload optionally) on Wrapper of vue-test-utils 断言事件在vue-test-utils的包装器上发出(可选地带有有效负载)
    // event.vue
    <template>
      <div @click="emitEvent('clicked')">
        Click Me
      </div>
    </template>
    
    <script>
    module.exports = {
      methods: {
        emitEvent (e) {
          this.$emit("special", e);
        }
      }
    }
    </script>
    import Component from "./event.vue";
    
    it("emits special event by click", () => {
      const wrapper = shallowMount(Component);
      wrapper.trigger("click");
      expect(wrapper).toHaveEmitted("special"); // Passes
      expect(wrapper).toHaveEmitted("special", "clicked"); // Passes
    });

    Vuex动作/变异 (Vuex actions/mutations)

    toDispatch (toDispatch)

    Assert the function dispatches Vuex action on the component 断言函数在组件上分派Vuex操作
    // click-store.vue
    <template>
      <div @click="dispatchStore('click')">
        Click Me
      </div>
    </template>
    
    <script>
    module.exports = {
      methods: {
        dispatchStore (e) {
          this.$store.dispatch('awesomeAction', e);
        }
      }
    }
    </script>
    import Component from "./click-store.vue";
    
    it("Dispatches the action on store by click", () => {
      const wrapper = shallowMount(Component);
      expect(() => {
        wrapper.trigger("click");
      }).toDispatch(wrapper, "awesomeAction"); // Passes
    
      expect(() => {
        wrapper.trigger("click");
      }).toDispatch(wrapper, "awesomeAction", 'click'); // Passes
    });

    Async function is supported as well.

    也支持异步功能。

    it("dispatches the action on store by click", async () => {
      return expect(async () => {
        dispatchEventAsynchronosly();
      }).toDispatch(wrapper, "awesomeAction", 'click'); // Passes
    });

    toCommit (TBD) (toCommit (TBD))

    Assert the store mutation is committed 断言商店突变已落实
    // click-store.vue
    <template>
      <div @click="commitStore('click')">
        Click Me
      </div>
    </template>
    
    <script>
    module.exports = {
      methods: {
        commitStore (e) {
          this.$store.commit('importantMutation', e);
        }
      }
    }
    </script>
    import Component from "./click-store.vue";
    
    it("Commits the mutation on store by click", () => {
      const wrapper = shallowMount(Component);
      expect(() => {
        wrapper.trigger("click");
      }).toCommit(wrapper, "importantMutation"); // Passes
    
      expect(() => {
        wrapper.trigger("click");
      }).toCommit(wrapper, "importantMutation", 'click'); // Passes
    });

    toHaveDispatched (toHaveDispatched)

    Assert a component has dispatched Vuex action 断言组件已调度Vuex操作
    // click-store.vue
    <template>
      <div @click="dispatchStore('click')">
        Click Me
      </div>
    </template>
    
    <script>
    module.exports = {
      methods: {
        dispatchStore (e) {
          this.$store.dispatch('awesomeAction', e);
        }
      }
    }
    </script>
    import Component from "./click-store.vue";
    import { vuexPlugin } from "jest-matcher-vue-test-utils";
    
    it("Dispatches the action on store by click", () => {
      const store = new Vuex.Store({
        actions: dispatchStore() {},
        plugins: [vuexPlugin()] // Requires adding plugin to use `toHaveDispatched` matcher
      });
    
      const wrapper = shallowMount(Component, { store })
      wrapper.trigger("click");
      expect(wrapper).toHaveDispatched("awesomeAction"); // Passes
      expect(wrapper).toHaveDispatched("awesomeAction", "click"); // Passes
    });

    道具验证 (Prop Validations)

    toBeValidProps (toBeValidProps)

    Assert that a prop set is valid for a component 断言道具集对组件有效
    // name-require-and-fullname-is-validated-component.vue
    props: {
      name: {
        type: String,
        required: true
      }
      fullname: {
        validator: function (val) {
          return !!val && val.match(/.+\s.+/);
        }
      }
    }
    import Component from "./name-require-and-fullname-is-validated-component.vue";
    
    it("component validates props", () => {
      expect(Component).toBeValidProps({ name: "required name", fullName: "Kengo Hamasaki" }); // Passes
      expect(Component).toBeValidProps({ fullName: "Kengo Hamasaki" }); // Fails
      expect(Component).toBeValidProps({ name: "required name", fullName: "Kengo" }); // Fails
    });

    toBeValidProp (toBeValidProp)

    Assert that a single prop is valid for a component 断言单个道具对组件有效
    // name-require-component.vue
    props: {
      name: {
        type: String,
        required: true
      }
    }
    import Component from "./name-require-component.vue";
    
    it("component validates props", () => {
      expect(Component).toBeValidProp("name", "Required Name"); // Passes
      expect(Component).toBeValidProp("name", null); // Fails as required
      expect(Component).toBeValidProp("name", 123}); // Fails as typecheck
    });

    toRequireProp (toRequireProp)

    Assert that a component requires a prop 断言组件需要道具
    // name-require-component.vue
    props: {
      name: {
        type: String,
        required: true
      }
    }
    import Component from "./name-require-component.vue";
    
    it("component requires name prop", () => {
      expect(Component).toRequireProp("name"); // Passes
      expect(Component).toRequireProp("birthday"); // Fails
    });

    toHaveDefaultProp (toHaveDefaultProp)

    Assert that a component gives default to a prop 断言组件默认为道具
    // default-address-component.vue
    props: {
      address: {
        type: String,
        default: "Kitakyushu, Japan"
      }
    }
    import Component from "./default-address-component.vue";
    
    it("component gives default value for address prop", () => {
      expect(Component).toHaveDefaultProp("address", "Kitakyushu, Japan"); // Passes
      expect(Component).toHaveDefaultProp("address", "San Francisco, US"); // Fails
    });

    toBeValidPropWithTypeCheck (toBeValidPropWithTypeCheck)

    Assert that a component validates a prop with type 断言组件验证类型的道具
    // takes-zipcode-component.vue
    props: {
      zipcode: {
        type: String
      }
    }
    import Component from "./takes-zipcode-component.vue";
    
    it("component validates zipcode prop", () => {
      expect(Component).toBeValidPropWithTypeCheck("zipcode", "94103"); // Passes
      expect(Component).toBeValidPropWithTypeCheck("zipcode", 94103); // Fails
    });

    toBeValidPropWithCustomValidator (toBeValidPropWithCustomValidator)

    Assert that a component validates a prop with custom validator 断言组件使用自定义验证器验证道具
    // fullname-is-validated-component.vue
    props: {
      fullname: {
        validator: function (val) {
          return !!val && val.match(/.+\s.+/);
        }
      }
    }
    import Component from "./fullname-is-validated-component.vue";
    
    it("component validates fullname prop", () => {
      expect(Component).toBeValidPropWithCustomValidator("fullname", "Kengo Hamasaki"); // Passes
      expect(Component).toBeValidPropWithCustomValidator("fullname", "Kengo"); // Fails
    });

    设定档 (Config)

    We can configure the matchers. Currently accepting mountOptions property to give options for shallowMount which is running in inside of matchers.

    我们可以配置匹配器。 当前接受mountOptions属性来为在匹配器内部运行的shallowMount提供选项。

    import vueTestUtilMatchers, { config } from "jest-matcher-vue-test-utils";
    import { createLocalVue } from "@vue/test-utils";
    
    config({
      mountOptions: { localVue: createLocalVue() }
    });

    翻译自: https://vuejsexamples.com/cute-jest-matchers-to-test-vue-components-with-vue-test-utils/

    展开全文
  • Jest,karma结合Vue-test-utils的优秀文章

    千次阅读 2018-06-21 17:40:50
  • vue-test-utils-next Vue测试实用程序的下一个迭代。它以Vue 3为目标。 安装及使用 毛线: yarn add @vue/test-utils@next --dev npm: npm install @vue/test-utils@next --save-dev 开始使用。 来自Vue 2 + Vue...
  • 一、起步 1. jest Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动...Vue Test UtilsVue.js 官方的单元测试实用工具库,为jest和vue提供了一个桥梁,暴露出一些接口,让我们更加方便的通过Jest...

    一、起步

    1. jest

    Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,配置较少,对vue框架友好。

    2. vue-test-utils

    Vue Test Utils 是 Vue.js 官方的单元测试实用工具库,为jest和vue提供了一个桥梁,暴露出一些接口,让我们更加方便的通过Jest为Vue应用编写单元测试。

    3. 安装

    如果已经安装配置了webpack、babel的vue脚手架,现在需要在安装的是:

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

    4. 在package.json中定义单元测试的脚本和配置jest

    "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.js

    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,运行单元测试

     

    二、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!')。

     

     三、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

     

    转载于:https://www.cnblogs.com/bear-blogs/p/10543263.html

    展开全文
  • vue-unit-testing-cheat-sheet:我的备忘单,用于通过jest和vue-test-utils测试vue组​​件
  • Vue Test Utils Vue Test UtilsVue.js 官方的单元测试实用工具库。它提供了一系列非常方便的工具,使我们更加轻松的为Vue构建的应用来编写单元测试。主流的 JavaScript 测试运行器有很多,但 Vue Test Utils 都...

    一.Jest 单元测试术语解析:describe、it、expect、test

    jest测试代码片段

    describe("number test", ()=>{
    	it('1 is true', ()=>{
    		expect(1).toBeTruthy()
    	})
    	test('2 is true',()=>{
    		expect(2).toBeTruthy()
    	})
    })
    
    
    • describe 描述, decribe会形成一个作用域
    • it 断言
    • expect 期望
    • test 测试,类似it

    二. Vue Test Utils

    Vue Test Utils 是 Vue.js 官方的单元测试实用工具库。它提供了一系列非常方便的工具,使我们更加轻松的为Vue构建的应用来编写单元测试。主流的 JavaScript 测试运行器有很多,但 Vue Test Utils 都能够支持。它是测试运行器无关的。

    简单的理解:Vue-test-utils在Vue和Jest之前提供了一个桥梁,暴露出一些接口,让我们更加方便的通过Jest为Vue应用编写单元测试

    三. 常用的API

    1.mount挂载组件

    创建一个包含被挂载和渲染的 Vue 组件的 Wrapper

    import { mount } from "@vue/test-utils";
    import Counter from "@/views/Counter.vue";
    // 通过npm install sinon安装
    import sinon from "sinon";
    
    describe("Counter.vue", () => {
        const change = sinon.spy();
        // 监听 Counter 里面的改变事件
        const wrapper = mount(Counter, {
            listeners: {
                change
            }
        });
    
     // 现在挂载组件,你便得到了这个包裹器
      const wrapper = mount(Counter)
    
      it('renders the correct markup', () => {
    
     //验证该组件渲染出来的 HTML 符合预期'<span class="count">0</span>'
        expect(wrapper.html()).toContain('<span class="count">0</span>')
      })
    
      // 也便于检查已存在的元素
      it('has a button', () => {
        expect(wrapper.contains('button')).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.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)
    

    4.选择器

    很多方法的参数中都包含选择器。一个选择器可以是一个 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')
    

    5.查找选项对象
    Name:可以根据一个组件的name选择元素。wrapper.find({ name: ‘my-button’ })
    Ref:可以根据$ref选择元素。wrapper.find({ ref: ‘myButton’ })

    而findAll返回的是一个数组,在选择有多个元素的情况下是不可以使用find的,在使用findAll后需要使用at()来选择具体序列的元素。

    在得到了我们的DOM元素之后我们就可以很方便地对属性以及内容进行断言判断。
    这里提一句,有关于样式的测试我更偏向于在E2E测试中去断言而不是在单元测试,这显得会更为直观,当然在单元测试中也提供了抓取class的API。
    有关于DOM的API列出了以下几个

    • attributes: 属性
    • classes:wrapper.classes()返回一个字符串数组,wrapper.classes(‘bar’)返回一个布尔值
    • contains:返回包含元素或组件匹配选择器
    • html: 以字符串形式返回DOM节点的HTML

    7.伪造 $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')
    

    四. 常用技巧

    下面简单列举几个,其他的请查看官网

    1.明白要测试的是什么
    不管业务内容多繁琐,只关注其输入和输出。

    2.测试组件渲染出来的 HTML
    import { mount } from ‘@vue/test-utils’
    import Counter from ‘./counter’

    describe(‘Counter’, () => {
    // 现在挂载组件,你便得到了这个包裹器
    const wrapper = mount(Counter)

    it(‘renders the correct markup’, () => {

    //验证该组件渲染出来的 HTML 符合预期’0
    expect(wrapper.html()).toContain(‘0’)
    })

    // 也便于检查已存在的元素
    it(‘has a button’, () => {
    expect(wrapper.contains(‘button’)).toBe(true)
    })
    })

    3.模拟用户交互
    wrapper.find() 定位该按钮,此方法返回一个该按钮元素的包裹器。然后我们能够通过对该按钮包裹器调用 .trigger() 来模拟点击。

    it(‘button click should increment the count’, () => {
    expect(wrapper.vm.count).toBe(0)
    const button = wrapper.find(‘button’)
    button.trigger(‘click’)
    expect(wrapper.vm.count).toBe(1)
    })

    4.使用 nextTick 编写异步测试代码
    更新会引发 DOM 变化的属性后必须等待一下。你可以使用 Vue.nextTick()

    it(‘updates text’, async () => {
    const wrapper = mount(Component)
    await wrapper.trigger(‘click’)
    expect(wrapper.text()).toContain(‘updated’)
    await wrapper.trigger(‘click’)
    wrapper.text().toContain(‘some different text’)
    })

    // 或者你不希望使用 async/await
    it(‘render text’, done => {
    const wrapper = mount(TestComponent)
    wrapper.trigger(‘click’).then(() => {
    wrapper.text().toContain(‘updated’)
    wrapper.trigger(‘click’).then(() => {
    wrapper.text().toContain(‘some different text’)
    done()
    })
    })
    })

    5.仿造 Prop
    使用 Vue 在内置 propsData 选项向组件传入 prop:

    import { mount } from ‘@vue/test-utils’

    mount(Component, {
    propsData: {
    aProp: ‘some value’
    }
    })

    你也可以用 wrapper.setProps({}) 方法更新这些已经挂载的组件的 prop。想查阅所有选项的完整列表,请移步该文档的挂载选项章节。

    6.测试键盘、鼠标等其它 DOM 事件
    触发事件:Wrapper 暴露了一个 trigger 方法。它可以用来触发 DOM 事件。 wrapper.find(‘button’)

    五 . vue结合Jest进行单元测试

    Vue引入Jest

    (1)使用vue-cli脚手架进行jest配置

    > vue create vue-jest
    > cd vuejest
    > vue add @vue/cli-plugin-unit-jest
    > npm install
    
    

    (2) 安装完成后package.json里会增加这些依赖
    在这里插入图片描述
    可以在后面加上:“test:unit”: “vue-cli-service test:unit --watchAll” 启动监听模式

    • 项目目录会增加出一个jest.config.js配置文件,根据项目需要修改配置
      参考API官网
    module.exports = {
    	//用作Jest配置基础的预设
    	preset: '@vue/cli-plugin-unit-jest',
    	//模块使用的文件扩展名数组。默认: ["js", "json", "jsx", "ts", "tsx", "node"] 如果您需要模块而未指定文件扩展名,则这些是Jest将按从左到右的顺序查找的扩展名。
    	moduleFileExtensions: ["js", "jsx", "json", "vue"],
    	moduleNameMapper: {
    		"^@/(.*)$": "<rootDir>/src/$1"
    	}
    }
    
    

    补充:

    首先我们需要安装jest需要的一些插件:

    • jest: Jest
    • @vue/test-utils:Vue Test Utils 是Vue.js 官方的单元测试实用工具库
    • babel-jest:使用Babel自动编译JavaScript代码
    • vue-jest:使用vue-jest去编译.vue 文件
    • jest-serializer-vue:生成vue快照的序列化器的模块,进行snapshot tests会需要
    • jest-transform-stub:处理css|图片|字体的预处理器jest-sonar-reporter(可选):
    • Jest的自定义结果处理器.处理器将Jest的输出转换为Sonar的通用测试数据格式.
    module.exports = {
      verbose: true,
      bail: 1,
      moduleFileExtensions: [
        'vue',
        'js',
        'json'
      ],
      moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1',
      },
      transform: {
        '.+\\.(css|less|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
        '^.+\\.js$': 'babel-jest',
        '.*\\.vue$': 'vue-jest',
        '^.+\\.svg$': 'jest-svg-sprite-loader'
      },
      snapshotSerializers: [ 'jest-serializer-vue' ],
      testResultsProcessor: 'jest-sonar-reporter',
      collectCoverage: true,
    
      collectCoverageFrom: [
        'src/**/*.{js,vue}',
      ],
      coverageReporters: ['html', 'lcov', 'text-summary'],
    
      coverageDirectory: './test/coverage',
      coveragePathIgnorePatterns: [ '/node_modules/' ],
      coverageThreshold: {
        global: {
          branches: 20,
          functions: 20,
          lines: 20,
          statements: 20
        }
      },
      testMatch: [
        '**/*.spec.js'
      ],
    };
    
    
    
    • verbose: 多于一个测试文件运行时展示每个测试用例测试通过情况
    • bail: 参数指定只要有一个测试用例没有通过,就停止执行后面的测试用例
    • moduleFileExtensions: jest 需要检测测的文件类型
    • moduleNameMapper: 从正则表达式到模块名称的映射,和webpack的alisa类似
    • transform: 预处理器配置
    • snapshotSerializers: Jest在快照测试中使用的快照序列化程序模块的路径列表
    • testResultsProcessor: 自定义结果处理器,用jest-sonar-reporter输出sonar需要的通用测试数据格式
    • collectCoverage: 是否进行覆盖率收集
    • collectCoverageFrom: 需要进行收集覆盖率的文件,会依次进行执行符合的文件
    • coverageReporters: Jest在编写覆盖率报告的配置,添加"text"或"text-summary"在控制台输出中查看覆盖率摘要
    • coverageDirectory: Jest输出覆盖信息文件的目录
    • coveragePathIgnorePatterns: 需要跳过覆盖率信息收集的文件目录
    • coverageThreshold: 覆盖结果的最低阈值设置,如果未达到阈值,jest将返回失败
    • testMatch: Jest用于检测测试的文件,可以用正则去匹配

    (3)项目目录多时会多出一个单元测试tests/uiit文件夹及样例测试代码

    在这里插入图片描述
    这边已经有一个针对helloword的测试用例
    在这里插入图片描述
    使用@vue/test-utils
    shallowMount 单元测试 :会使用占位符占住HelloWorld的子组件,只渲染HelloWorld这个组件

    六.简单demo

    1.测试监听器及模拟点击事件

    <template>
    	<div>
    		<span>count: {{ count }}</span>
    		<button @click="handleClick">count++</button>
    	</div>
    </template>
    
    <script>
    export default {
    	data() {
    		return {
    			count: 0
    		};
    	},
    	methods: {
    		handleClick() {
    			this.count++;
    			this.$emit("change", this.count);
    		}
    	}
    };
    </script>
    
    <style></style>
    
    
    

    编写单元测试代码Counter.spec.js

    import { mount } from "@vue/test-utils";
    import Counter from "@/views/Counter.vue";
    import sinon from "sinon";
    
    describe("Counter.vue", () => {
        const change = sinon.spy();
        const wrapper = mount(Counter, {
            listeners: {
                change
            }
        });
        it("renders counter html", () => {
            expect(wrapper.html()).toMatchSnapshot();
        });
        it("count++", () => {
    
            const button = wrapper.find("button");
            button.trigger("click");
            console.log(wrapper.vm.count, change.called, change.callCount, 'aaaaa')
            expect(wrapper.vm.count).toBe(1);
            expect(change.called).toBe(true);
            button.trigger("click");
            console.log(wrapper.vm.count, change.called, change.callCount, 'bbbb')
            expect(change.callCount).toBe(2);
            // 错误测试
            // button.trigger("click");
            // console.log(wrapper.vm.count, change.called, change.callCount, 'ccc')
            // expect(change.callCount).toBe(2);
        });
    });
    
    
    

    注意如果的click事件是子组件(按钮组件emit),DOM,使trigger,使emit)的事件,在父组件内不属于DOM原生事件,所以触发方式不能使用trigger,而应该使用emit

    2. 测试DOM结构
    通过mount、shallow、find、findAll方法都可以返回一个包裹器对象,包裹器会暴露很多封装、遍历和查询其内部的Vue组件实例的便捷的方法。

    其中,find和findAll方法都可以都接受一个选择器作为参数,find方法返回匹配选择器的DOM节点或Vue组件的Wrapper,findAll方法返回所有匹配选择器的DOM节点或Vue组件的Wrappers的WrapperArray。
    在这里插入图片描述
    注意:红色框,获取到正确的wrapper.classes() 的值

    3.测试Props

    父组件向子组件传递数据使用Props,而子组件向父组件传递数据则需要在子组件出发父组件的自定义事件

    当测试对父组件向子组件传递数据这一行为时,我们想要测试的当我们传递给子组件一个特定的参数,子组件是否会按照我们所断言的那样变现。

    在初始化时向子组件传值,使用的方法是propsData。
    在这里插入图片描述

    也可以使用setProps方法:

     it('renders props.msg when passed', async () => {
            const msg = 'new message';
            const wrapper = mount(Setprops)
            await wrapper.setProps({ msg: 'new message' })
    
            console.log("wrapper.text():", wrapper.text())
            expect(wrapper.text()).toMatch(msg)
        })
    

    4.测试方法

    单元测试的核心之一就是测试方法的行为是否符合预期,在测试时要避免一切的依赖,将所有的依赖都mock掉。

    这个例子里面,我们仅仅关注测试getAnswer方法,其他的忽略掉。

    七. 补充

    (1)测试辅助工具Sinon

    Sinon是用来辅助我们进行前端测试的,在我们的代码需要与其他系统或者函数对接时,它可以模拟这些场景,从而使我们测试的时候不再依赖这些场景。
    Sinon有主要有三个方法辅助我们进行测试:spy,stub,mock。

    安装sinon
    npm install --save-dev sinon

    1. sinon.spy()
      spy一般有两种玩法,一种是生成一个新的匿名间谍函数,另外一种是对原有的函数进行封装并进行监听。
     const wrapper = mount(Counter, {
            listeners: {
                change
            }
        });
    

    sinon.spy()会产生一个函数对象,当调用这个函数对象后,这个函数对象通过called可以返回一个bool值,表示函数是否被调用。
    expect(change.called).toBe(true); // console.log(change.called) 是true ,所以断言匹配就是true 正确

    1. stub
      是带有预编程行为的函数,就是spy的加强版,不仅完全支持spy的各种操作,还能操作函数的行为。和spy一样,stub也能匿名,也能去封住并监听已有函数。然而有一点和spy不同,当封装了一个已有函数后,原函数不会再被调用。
      使用stub来嵌入或者直接替换掉一些代码,来达到隔离的目的。简单的说,stub是代码的一部分。在运行时用stub替换真正代码,忽略调用代码的原有实现。目的是用一个简单一点的行为替换一个复杂的行为,从而独立地测试代码的某一部分。
      在这里插入图片描述
      sinon.stub(HTMLMediaElement.prototype, “play”):将HTMLMediaElement.prototype.play 替换成一个stub(),可将它替换成指定函数
      stub.restore():由于 stub 是使用指定函数替换已有的函数,所以每次使用后需要用stub.restore()复原它

    2. mock
      像spy和stub一样的伪装方法,如果mock没有得到期望的结果就会测试失败

    八 参考

    展开全文
  • vue一些utils

    2020-06-12 14:43:19
    utils1.blob2base642.base64 字符串之间的转化3格式化日期相关4防抖与节流 1.blob2base64 let blob2base64 = async (blobData, type = 'image/jpeg') => { let blob = new Blob([blobData], { type: type }) ...
  • by Edd Yerburgh 埃德·... 使用Tape和Vue Test Utils编写快速的Vue单元测试 (Write blazing fast Vue unit tests with Tape and Vue Test Utils) Tape is the fastest framework for unit testing Vue components. ...
  • Vue Router test utils

    2020-12-09 14:16:16
    I think we need both good utils for testing with the actual VueRouter, and a mocked VueRouter. <p>This is the kind of thing you would previously write in VTU 1.x with Vue 2.x and Vue Router 3.x: ...
  • Vue单元测试:Vue Test Utils + Jest

    千次阅读 2018-11-12 14:40:56
    1、 安装Jest和Vue Test Utils。 $ npm install --save-dev jest @vue/test-utils 2、安装vue-jest。.vue文件一般为单文件组件(Single-File Componengts),即html,js,css在一个文件中,vue-jest告诉Jest如何...
  • Vue Test UtilsVue.js的官方测试库。 配套 该存储库提供以下两个软件包: 您可以通过以下命令安装这些软件包。 npm install --save-dev @vue/test-utils npm install --save-dev @vue/server-test-utils 对等...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,888
精华内容 1,555
关键字:

utilsvue

vue 订阅