精华内容
下载资源
问答
  • 主要介绍了浅谈Vue组件单元测试究竟测试什么,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 主要介绍了使用Karma做vue组件单元测试的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 本文首发于Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试 | 吕立青的博客 欢迎关注知乎专栏 —— 前端的逆袭(凡可 JavaScript,终将 JavaScript。) 欢迎关注我的博客, 知乎 ,GitHub,掘金。 本文的目标 ...

    本文首发于Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试 | 吕立青的博客

    欢迎关注知乎专栏 —— 前端的逆袭(凡可 JavaScript,终将 JavaScript。)

    欢迎关注我的博客知乎GitHub掘金


    本文的目标

    2.1 在Vue应用的单元测试中,对不同UI组件的单元测试有何不同?颗粒度该细到什么样的程度?

    // Given
    一个有基本的UT知识但没写过Vue测试的新人?
    // When
    当他?阅读和练习本文的Vue单元测试的部分
    // Then
    当然,他能够学会Vue组件在测试当中的几种渲染方式
    他能够学会UI组件的分类,特别是交互行为的测试方式
    复制代码

    组件化与 UI 测试

    在组件化出现之前,我们都压根不谈 UI 的单元测试,哪怕是对于 UI 页面层级的测试来说都是一件非常困难的事情。其实组件化并不全是为了复用,很多情况下也恰恰是为了分治,从而我们可以分组件对 UI 页面进行开发,然后分别对其进行单元测试。

    前端组件化已经让 UI 测试变得容易很多,每个组件都可以被简化为这样一个表达式,即 UI = f(data),这个纯函数返回的只是一个描述 UI 组件应该是什么样子的虚拟 DOM,本质上就是一个树形的数据结构。给这个纯函数输入一些应用程序的状态,就会得到相应的 UI 描述的输出,这个过程不会去直接操作实际的 UI 元素,也不会产生所谓的副作用。

    Vue 组件树的测试

    按理来说按照纯函数这样的思路,Vue 组件的测试应该很简单的说。但与此同时,对 UI 渲染的组件树进行测试依然存在一个问题,从下图中可以看出,越处于上层的组件,其复杂度必然会随之提高。对于最底层的子组件来说,我们可以很容易得将其进行渲染并测试其逻辑的正确与否,但对于较上层的父组件来说,通常来说就需要对其所包含的所有子组件都进行预先渲染,甚至于最上面的组件需要渲染出整个 UI 页面的真实 DOM 节点才能对其进行测试,这显然是不可取的。

    在单元测试中,通常我们希望将重点放在作为独立单元进行测试的组件上,并避免间接断言其子组件的行为。此外,对于包含许多子组件的组件,整个 render 树会变得非常之大,而反复 render 所有的子组件可能会减慢单元测试的速度。

    而根据 Mike Cohn 的测试金字塔中所提到的两件事:

    • 编写不同粒度的测试
    • 层次越高,你写的测试应该越少

    为了维持金字塔形状,一个健康、快速、可维护的测试组合应该是这样的:写许多小而快的单元测试。适当写一些更粗粒度的测试,写很少高层次的端到端测试。注意不要让你的测试变成冰淇淋那样子,这对维护来说将是一个噩梦,并且跑一遍也需要太多时间。(via 测试金字塔实战 – ThoughtWorks洞见

    对于 Vue 组件树来说,浅渲染(Shallow Rendering)解决了这个问题,也就是说在我们针对某个上层组件进行测试时,可以不用渲染它的子组件,所以就不用再担心子组件的表现和行为,这样就可以只对特定组件的逻辑及其渲染输出进行测试了。Vue 官方提供了 @vue/test-utils 可以让我们使用浅渲染这个特性,用于测试虚拟 DOM 对象,即 Vue.component 的实例。

    import { shallowMount } from '@vue/test-utils'
    
    const wrapper = shallowMount(Component)
    wrapper.vm // the mounted Vue instance
    复制代码

    Vue 组件的渲染方式

    浅渲染 shallowMount(component[, options]) => Wrapper

    浅渲染在将一个组件作为一个单元进行测试的时候非常有用,可以确保你的测试不会去间接断言子组件的行为。shallowMount 方法就是 Shallow Rendering 的封装,shallowMountmount 类似返回 mountedrendered Vue 组件的 Wrapper,但只会渲染出组件的第一层 DOM 结构,其嵌套的子组件不会被渲染出来,从而使得渲染的效率更高,单元测试的速度也会更快。

    import { shallowMount } from '@vue/test-utils'
    
    describe('Vue Component shallowMount', () => {
      it('should have three <todo /> components', () => {
        const wrapper = shallowMount(App)
        expect(wrapper.find({ name: 'Todo' })).toHaveLength(3)
      })
    }
    复制代码

    全量渲染 mount(component[, options]) => Wrapper

    mount 方法则会将 Vue 组件和所有子组件渲染为真实的 DOM 节点,特别是在你依赖真实的 DOM 结构必须存在的情况下,比如说按钮的点击事件。完全的 DOM 渲染需要在全局范围内提供完整的 DOM API, 这也就意味着 Vue Test Utils 依赖于浏览器环境。

    从技术上讲,你可以在真实的浏览器中运行,但由于在不同平台上启动真实浏览器的复杂性,更建议使用 JSDOM 在虚拟浏览器环境中运行 Node 中的测试。推荐使用 mount 的方法是依赖于一个名为 jsdom的库,它本质上是一个完全在 JavaScript 中实现的 headless 浏览器。

    import { mount } from '@vue/test-utils'
    
    describe('Vue Component Mount', () => {
      it('should delete Todo when click button', () => {
        const wrapper = mount(App)
        const todoLength = wrapper.find('li').length
        wrapper.find('button.delete').at(0).trigger('click')
        expect(wrapper.find('li').length).toEqual(todoLength - 1)
      })
    })
    复制代码

    静态渲染 render(component[, options]) => CheerioWrapper

    render 方法则会将 Vue 组件渲染成静态的 HTML 字符串,而返回的则是一个 Cheerio 实例对象,采用的是一个第三方的 HTML 解析库 Cheerio,这是一个类 jQuery 的库,可以在 Node.js 中遍历 DOM。渲染后所返回的 CheerioWrapper 可以用于分析最终结果的 HTML 代码结构,好处是它的 API 跟 shallowMountmount 方法的 API 都基本保持一致。

    import { render } from '@vue/test-utils'
    
    describe('Vue Component Render', () => {
      it('should not have .todo-done class', () => {
        const wrapper = render(App)
        expect(wrapper.find('.todo-done').length).toEqual(0)
        expect(wrapper.text()).toContain('<div class="todo"></div>')
      })
    })
    复制代码

    纯字符串渲染 renderToString(component[, options]) => string

    renderToString 很简单,顾名思义就是把一个组件渲染成对应的 HTML 字符串,在此不再赘述。

    import { renderedString } from '@vue/test-utils'
    
    describe('Vue Component renderedString', () => {
      it('should have .todo class', () => {
        const renderedString = renderToString(App)
        expect(renderedString).toContain('<div class="todo"></div>')
      })
    })
    复制代码

    实例 Wrapper find() 方法与选择器

    从前面的示例代码中可以看到,无论哪种渲染方式所返回的 wrapper 都有一个 .find() 方法,它接受一个 selector 参数,然后返回一个对应的 wrapper 对象。而 .findAll() 则会返回一个类型相同的 wrapper 对象数组,里面包含了所有符合条件的子组件。在这个对象数组的基础上,at 方法则可以返回指定位置的子组件,trigger 方法用于在组件之上模拟触发某种行为。

    @vue/test-utils 中的 Selectors 即选择器,既可以是 CSS 选择器(也支持比较复杂的关系选择器组合),也可以是 Vue 组件 或是一个 option 对象,以便于在 wrapper 对象中可以轻松地指定想要查找的节点。

    /* CSS Selector */
    wrapper.find('.foo') //class syntax
    wrapper.find('input') //tag syntax
    wrapper.find('#foo') //id syntax 
    wrapper.find('[foo="bar"]') //attribute syntax
    wrapper.find('div:first-of-type') //pseudo selectors
    复制代码

    在下面的示例中,我们可以通过 Vue 组件构造函数的引用找到该组件,与此同时也可以基于 Vue 组件属性的子集来查找组件和节点,或者通过根据 $ref 选择相应元素。

    /* Component Constructor */
    import foo from './foo.vue'
    
    const wrapper = shallowMount(app)
    expect(wrapper.find(foo).is(foo)).toBe(true)
    
    /* Find Option Object */
    const wrapper = appWrapper.find({ name: 'my-button' })
    wrapper.trigger('click')
    
    /* Find by refs */
    const wrapper = appWrapper.find({ ref: 'myButton' })
    wrapper.trigger('click')
    复制代码

    UI 组件交互行为的测试

    我们不但可以通过 find 方法查找 DOM 元素,还可以通过 trigger 方法在组件上模拟触发某个 DOM 事件,比如 Click,Change 等等。对于浅渲染来说,事件模拟并不会像真实环境中所预期的那样进行传播,因此我们必须在一个已经设置好了事件处理方法的实际节点上才能够调用,实际上 .trigger() 方法将会根据模拟的事件触发这个组件的 prop。例如,.trigger('click') 实际上会获取 对应的 clickHandler propsData 并调用它。

    it('should trigger event when click button', () => {  
      const clickHandler = jest.fn()
      const wrapper = shallowMount(Foo, {
        propsData: { clickHandler }
      })
      wrapper.trigger('click')
      expect(clickHandler).toHaveBeenCalled()
    })
    复制代码

    关于 nextTick 怎么办?

    Vue 会异步的将未生效的 DOM 更新批量应用,以避免因数据反复突变而导致的无谓的重新渲染。这也是为什么在实践过程中我们经常在触发状态改变后用 Vue.nextTick 来等待 Vue 把实际的 DOM 更新做完的原因。

    为了简化用法,Vue Test Utils 同步应用了所有的更新,所以你不需要在测试中使用 Vue.nextTick 来等待 DOM 更新。

    注意:当你需要为诸如异步回调或 Promise 解析等操作显性改进为事件循环的时候,nextTick 仍然是必要的。

    总结一下

    Vue 组件的单元测试是前端 UI 测试组合的基石,单元测试保证了代码库里的每个组件(被测试的主体)都能按照预期那样工作,它的数量在测试组合中应该远远多于其他类型的测试。其实呢,也不要太拘泥于测试金字塔中各层次的名字,UI 测试显然不必位于金字塔的最高层,你也完全可以用 Cypress、Nightwatch 这样的 E2E 框架对 UI 进行单元测试,这个的话我们就留到后面再聊。

    未完待续……

    ## 单元测试基础

    • [x] ### 单元测试与自动化的意义
    • [x] ### 为什么选择 Jest
    • [x] ### Jest 的基本用法
    • [x] ### 该如何测试异步代码?

    ## Vue 单元测试

    • [x] ### Vue 组件的渲染方式
    • [x] ### Wrapper find() 方法与选择器
    • [x] ### UI 组件交互行为的测试

    ## Vuex 单元测试

    • [ ] ### CQRS 与 Redux-like 架构
    • [ ] ### 如何对 Vuex 进行单元测试
    • [ ] ### Vue组件和Vuex store的交互

    ## Vue应用测试策略

    • [ ] ### 单元测试的特点及其位置
    • [ ] ### 单元测试的关注点
    • [ ] ### 应用测试的测试策略

    本文首发于Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试 | 吕立青的博客

    欢迎关注知乎专栏 —— 前端的逆袭(凡可 JavaScript,终将 JavaScript。)

    欢迎关注我的博客知乎GitHub掘金

    展开全文
  • Vue 组件单元测试究竟测试什么?

    千次阅读 2020-02-07 15:03:49
    关于 Vue 组件单元测试最常见的问题就是“我究竟应该测试什么?” 虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃。 在...

    原文:https://vuejsdevelopers.com/2019/08/26/vue-what-to-unit-test-components
    翻译:1024译站

    关于 Vue 组件单元测试最常见的问题就是“我究竟应该测试什么?”

    虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃。

    在本文中,我将分享一些用于组件单元测试的指导原则,这些指导原则可以确保在编写测试上不会花费大量时间,但是可以提供足够的覆盖率来避免错误。

    本文假设你已经了解 Jest 和 Vue Test Utils。

    示例组件

    在学习这些指导原则之前,我们先来熟悉下要测试的示例组件。组件名为Item.vue ,是 eCommerce App 里的一个产品条目。
    Example component

    下面是组件的源码。注意有三个依赖项:Vuex ($store), Vue Router ($router) 和 Vue Auth ($auth)。

    Item.vue

    <template>
      <div>
        <h2>{{ item.title }}</h2>
        <button @click="addToCart">Add To Cart</button>
        <img :src="item.image"/>
      </div>
    </template>
    <script>
    export default {
      name: "Item",
      props: [ "id" ],
      computed: {
        item () {
          return this.$store.state.find(
            item => item.id === this.id
          );
        }
      },
      methods: {
        addToCart () {
          if (this.$auth.check()) {
            this.$store.commit("ADD_TO_CART", this.id);
          } else {
            this.$router.push({ name: "login" });
          }
        }
      }
    };
    </script>
    
    

    配置 Spec 文件

    下面是测试用的 spec 文件。其中,我们将用 Vue Test Utils “浅挂载”示例组件,因此引入了相关模块以及我们要测试的 Item 组件。

    同时还写了一个工厂函数用于生成可覆盖的配置对象,以免在每个测试中都需要指定 props 和 mock 三个依赖项。
    item.spec.js

    import { shallowMount } from "@vue/test-utils";
    import Item from "@/components/Item";
    
    function createConfig (overrides) {
      const id = 1;
      const mocks = {
        // Vue Auth
        $auth: {
          check: () => false
        },
        // Vue Router
        $router: {
          push: () => {}
        },
        // Vuex
        $store: {
          state: [ { id } ],
          commit: () => {}
        }
      };
      const propsData = { id };
      return Object.assign({ mocks, propsData }, overrides);
    }
    
    describe("Item.vue", () => {
      // Tests go here
    });
    
    

    确定业务逻辑

    对于要测试的组件,要问的第一个也是最重要的问题是“业务逻辑是什么”,即组件是做什么的?

    对于这个Item.vue,业务逻辑是:

    • 根据接收的id属性展示条目信息
    • 如果用户是访客,点击 Add to Cart 按钮将重定向到登录页
    • 如果用户已登录,点击 Add to Cart 按钮会触发 Vuex mutation  ADD_TO_CART

    确定输入和输出

    当你对组件做单元测试时,可将其视为一个黑盒。方法、计算属性等内部逻辑只影响输出。

    因此,下一个重点是确定组件的输入和输出,因为这些也是测试的输入和输出。

    Item.vue 的输入是:

    • id 属性
    • 来自 Vuex 和 Vue Auth 的数据状态
    • 用户点击按钮

    输出是:

    • 渲染后的 HTML
    • 发送到 Vuex mutation 或者 Vue Router push 的数据

    有些组件也会将表单和事件作为输入,触发事件作为输出。

    测试 1: 访客点击按钮跳转路由

    有一个业务逻辑是“如果用户是访客,点击 Add to Cart 按钮将重定向到登录页”。我们来写这个测试。

    我们通过“shallow mount”组件来编写测试,然后找到并点击Add to Cart 按钮。

    test("router called when guest clicks button", () => {
      const config = createConfig();
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      // Assertion goes here
    }
    
    

    随后我们会加上 assertion。

    不要超出输入和输出的界限

    在这个测试中很容易采取的做法是在点击按钮后判断路由是否跳转到了登录页,比如:

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      // 错!
      const route = router.find(route => route.name === "login");
      expect(wrapper.vm.$route.path).toBe(route.path);
    }
    
    

    虽然这确实也能测试组件的输出,但是它依赖于路由功能,这不应该是组件所关心的。

    直接测试组件的输出会更好,也就是调用了 $router.push。至于路由是否最终完成了操作,这已经超出了本测试的范畴。

    因此我们可以监听路由的 push 方法,并断言它是否被登录路由对象调用。

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      jest.spyOn(config.mocks.$router, "push");
      const route = router.find(route => route.name === "login");
      expect(spy).toHaveBeenCalledWith(route);
    }
    
    

    测试 2: 登录用户点击按钮后调用 vuex

    接下来让我们测试业务逻辑“如果用户已登录,点击 Add to Cart 按钮将触发 Vuex mutation ADD_TO_CART”。

    同样,你不需要判断 Vuex 状态是否更改了。要验证这个需要另外单独测试 Vuex store。

    组件的职责只是执行 commit,因此我们只要测试这个动作就行。

    首先重写 $auth.check 假数据让它返回 true (模拟登录用户)。然后监听 store 的 commit 方法,并断言点击按钮后被调用。

    test("vuex called when auth user clicks button", () => {
      const config = createConfig({
        mocks: {
          $auth: {
            check: () => true
          }
        }
      });
      const spy = jest.spyOn(config.mocks.$store, "commit");
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      expect(spy).toHaveBeenCalled();
    }
    
    

    不要测试其他库的功能

    Item 组件展示条目数据,特别是标题和图片。或许我们应该写一个测试来专门检查这些?比如:

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      // Wrong
      expect(wrapper.find("h2").text()).toBe(item.title);
    }
    
    

    这又是一个不必要的测试,因为它只是测试了 Vue 从 Vuex 中提取数据并插入到模板的能力。Vue 这个库已经对该机制进行了测试,所以你应该依赖于它。

    测试 3: 正确地渲染

    但是等等,如果有人不小心将title重命名为name,然后忘记更新插值表达式怎么办?这难道不需要测试吗?

    没错,但是如果你像这样来测试模板的方方面面,何时才是个头?

    测试 HTML 最好的办法是使用快照,用来检查整体渲染后的结果。这不仅覆盖了标题插值,还包括图片、按钮文本、任何 class 等。

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      expect(wrapper).toMatchSnapshot();
    });
    
    

    其他不需要测试的点还有这些:

    • src 属性是否绑定到 img 元素
    • 添加到 Vuex store 中的数据是否跟插入的数据一致
    • 计算属性是否返回了正确的数据
    • 执行 router push 是否重定向到正确的页面

    诸如此类。

    总结

    我认为上面三个简单的测试对这个组件来说足够了。

    组件单元测试的一个好理念是先假设测试是不必要的,除非被证明是必要的。

    你可以问自己以下问题:

    • 这是业务逻辑的一部分吗?
    • 这是直接测试组件的输入和输出吗?
    • 这是测试自己的代码,还是第三方代码?

    让我们愉快地单元测试吧!

    更多前端技术干货尽在微信公众号:1024译站
    微信公众号:1024译站

    展开全文
  • 掘金文章专栏:KaysonLi 的个人主页 - 专栏 - 掘金关于 Vue 组件单元测试最常见的问题就是“我究竟应该测试什么?”虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的...

    2e5a6c18e0d288863c97442ad3a65aa8.png

    作者简介:

    李中凯

    八年多工作经验 前端负责人,

    擅长JavaScript/Vue。

    掘金文章专栏:KaysonLi 的个人主页 - 专栏 - 掘金


    关于 Vue 组件单元测试最常见的问题就是“我究竟应该测试什么?”

    虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃。

    本文将分享一些用于组件单元测试的指导原则,这些指导原则可以确保在编写测试上不会花费大量时间,但是可以提供足够的覆盖率来避免错误。

    本文假设你已经了解 Jest 和 Vue Test Utils。

    示例组件

    在学习这些指导原则之前,我们先来熟悉下要测试的示例组件。组件名为Item.vue ,是 eCommerce App 里的一个产品条目。

    8ea3eef3a0f76eab508ecb156d6d0d6e.png

    下面是组件的源码。注意有三个依赖项:Vuex ($store), Vue Router ($router) 和 Vue Auth ($auth)。

    Item.vue

    <template>
      <div>
        <h2>{{ item.title }}</h2>
        <button @click="addToCart">Add To Cart</button>
        <img :src="item.image"/>
      </div>
    </template>
    <script>
    export default {
      name: "Item",
      props: [ "id" ],
      computed: {
        item () {
          return this.$store.state.find(
            item => item.id === this.id
          );
        }
      },
      methods: {
        addToCart () {
          if (this.$auth.check()) {
            this.$store.commit("ADD_TO_CART", this.id);
          } else {
            this.$router.push({ name: "login" });
          }
        }
      }
    };
    </script>
    
    

    配置 Spec 文件

    下面是测试用的 spec 文件。其中,我们将用 Vue Test Utils “shallow mount”该示例组件,因此引入了相关模块以及我们要测试的 Item 组件。

    同时还写了一个工厂函数用于生成可重
    写的配置对象,以免在每个测试中都需要指定 props 和 mock 三个依赖项。item.spec.js

    import { shallowMount } from "@vue/test-utils";
    import Item from "@/components/Item";
    
    function createConfig (overrides) {
      const id = 1;
      const mocks = {
        // Vue Auth
        $auth: {
          check: () => false
        },
        // Vue Router
        $router: {
          push: () => {}
        },
        // Vuex
        $store: {
          state: [ { id } ],
          commit: () => {}
        }
      };
      const propsData = { id };
      return Object.assign({ mocks, propsData }, overrides);
    }
    
    describe("Item.vue", () => {
      // Tests go here
    });
    
    

    确定业务逻辑

    对于要测试的组件,要问的第一个也是最重要的问题是“业务逻辑是什么”,即组件是做什么的?

    对于这个Item.vue,业务逻辑是:

    • 根据接收的id属性展示条目信息
    • 如果用户是访客,点击 Add to Cart 按钮将重定向到登录页
    • 如果用户已登录,点击 Add to Cart 按钮会触发 Vuex mutation ADD_TO_CART

    确定输入和输出

    当你对组件做单元测试时,可将其视为一个黑盒。方法、计算属性等内部逻辑只影响输出。

    因此,下一个重点是确定组件的输入和输出,因为这些也是测试的输入和输出。

    Item.vue 的输入是:

    • id 属性
    • 来自 Vuex 和 Vue Auth 的数据状态
    • 用户点击按钮

    输出是:

    • 渲染后的 HTML
    • 发送到 Vuex mutation 或者 Vue Router push 的数据
    有些组件也会将表单和事件作为输入,触发事件作为输出。

    测试 1: 访客点击按钮后跳转路由

    有一个业务逻辑是“如果用户是访客,点击 Add to Cart 按钮将重定向到登录页”。我们来写这个测试。

    我们通过“shallow mount”该组件来编写测试,然后找到并点击Add to Cart 按钮。

    test("router called when guest clicks button", () => {
      const config = createConfig();
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      // Assertion goes here
    }
    
    

    随后我们会加上断言。

    不要超出输入和输出的界限

    在这个测试中很容易采取的做法是在点击按钮后判断路由是否跳转到了登录页,比如:

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      // 错!
      const route = router.find(route => route.name === "login");
      expect(wrapper.vm.$route.path).toBe(route.path);
    }
    
    

    虽然这确实也能测试组件的输出,但是它依赖于路由功能,这不应该是组件所关心的。

    直接测试组件的输出会更好,也就是调用了 $router.push。至于路由是否最终完成了操作,这已经超出了本测试的范畴。

    因此我们可以监听路由的 push 方法,并断言它是否被登录路由对象调用。

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      jest.spyOn(config.mocks.$router, "push");
      const route = router.find(route => route.name === "login");
      expect(spy).toHaveBeenCalledWith(route);
    }
    
    

    测试 2: 登录用户点击按钮后调用 vuex

    接下来让我们测试业务逻辑“如果用户已登录,点击 Add to Cart 按钮将触发 Vuex mutation ADD_TO_CART”。

    同样,你不需要判断 Vuex 状态是否更改了。要验证这个需要另外单独测试 Vuex store。

    组件的职责只是执行 commit,因此我们只要测试这个动作就行。

    首先重写 $auth.check 假数据让它返回 true (模拟登录用户)。然后监听 store 的 commit 方法,并断言点击按钮后被调用。

    test("vuex called when auth user clicks button", () => {
      const config = createConfig({
        mocks: {
          $auth: {
            check: () => true
          }
        }
      });
      const spy = jest.spyOn(config.mocks.$store, "commit");
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      expect(spy).toHaveBeenCalled();
    }
    
    

    不要测试其他库的功能

    Item 组件展示条目数据,特别是标题和图片。或许我们应该写一个测试来专门检查这些?比如:

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      // Wrong
      expect(wrapper.find("h2").text()).toBe(item.title);
    }
    
    

    这又是一个不必要的测试,因为它只是测试了 Vue 从 Vuex 中提取数据并插入到模板的能力。Vue 这个库已经对该机制进行了测试,所以你应该直接依赖于它。

    测试 3: 正确地渲染

    但是等等,如果有人不小心将title重命名为name,然后忘记更新插值表达式怎么办?这难道不需要测试吗?

    没错,但是如果你像这样来测试模板的方方面面,什么时候才是个头?

    测试 HTML 最好的办法是使用快照,用来检查整体渲染后的结果。这不仅覆盖了标题插值,还包括图片、按钮文本、任何 class 等。

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      expect(wrapper).toMatchSnapshot();
    });
    
    

    其他不需要测试的点还有这些:

    • src 属性是否绑定到 img 元素
    • 添加到 Vuex store 中的数据是否跟插入的数据一致
    • 计算属性是否返回了正确的数据
    • 执行 router push 是否重定向到正确的页面

    诸如此类。

    总结

    我认为上面三个简单的测试对这个组件来说足够了。组件单元测试的一个好理念是先假设测试是不必要的,除非被证明是必要的。你可以问自己以下问题:

    • 这是业务逻辑的一部分吗?
    • 这是直接测试组件的输入和输出吗?
    • 这是测试自己的代码,还是第三方代码?

    作者简介:

    李中凯

    八年多工作经验 前端负责人,

    擅长JavaScript/Vue。

    掘金文章专栏:KaysonLi 的个人主页 - 专栏 - 掘金

    本文已经获得李中凯老师授权转发,其他人若有兴趣转载,请直接联系作者授权。

    更多的学习资料,请看这里:

    http://www.jnshu.com/login/1/36856070?source=zhihu-article-lizhongkai

    展开全文
  • 关于单元测试Vue组件,我看到的最常见的问题是“我到底应该测试什么?” 虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会在测试过多时犯错误。毕竟,没有人愿意成为一个组件测试不足导致应用程序在生产...

    关于单元测试Vue组件,我看到的最常见的问题是“我到底应该测试什么?”

    虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会在测试过多时犯错误。毕竟,没有人愿意成为一个组件测试不足导致应用程序在生产中崩溃的人。

    在本文中,我将与您分享一些用于单元测试组件的指导原则,这些指导原则确保我不会永远编写测试,但是提供了足够的覆盖率,使我不会遇到麻烦。

    我假设您已经了解了Jest和Vue测试Utils。

    示例组件

    在学习指导原则之前,让我们首先熟悉我们将要测试的以下示例组件。它叫做Item.vue,是电子商务应用中的产品项目。

    Item.vue

    <template>
      <div>
        <h2>{{ item.title }}</h2>
        <button @click="addToCart">Add To Cart</button>
        <img :src="item.image"/>
      </div>
    </template>
    <script>
    export default {
      name: "Item",
      props: [ "id" ],
      computed: {
        item () {
          return this.$store.state.find(
            item => item.id === this.id
          );
        }
      },
      methods: {
        addToCart () {
          if (this.$auth.check()) {
            this.$store.commit("ADD_TO_CART", this.id);
          } else {
            this.$router.push({ name: "login" });
          }
        }
      }
    };
    </script>

    规范文件设置

    下面是测试的规范文件。在其中,我们将使用Vue Test Utils浅挂载组件,因此我已经导入了它,以及我们正在测试的Item组件。

    我还创建了一个工厂函数,它将生成一个可覆盖的配置对象,这样我们就不必在每个测试中指定道具和模拟三个依赖项。

    item.spec.js

    import { shallowMount } from "@vue/test-utils";
    import Item from "@/components/Item";
    
    function createConfig (overrides) {
      const id = 1;
      const mocks = {
        // Vue Auth
        $auth: {
          check: () => false
        },
        // Vue Router
        $router: {
          push: () => {}
        },
        // Vuex
        $store: {
          state: [ { id } ],
          commit: () => {}
        }
      };
      const propsData = { id };
      return Object.assign({ mocks, propsData }, overrides);
    }
    
    describe("Item.vue", () => {
      // Tests go here
    });

    确定业务逻辑

    关于要测试的组件,要问的第一个也是最重要的问题是“什么是业务逻辑?”换句话说,组件的作用是什么?

    对于Item.vue,这是业务逻辑:

    • 它将根据接收到的id道具显示一个项目

    • 如果用户是访客,单击Add to Cart按钮将其重定向到登录页面

    • 如果用户已登录,单击Add to Cart按钮将触发Vuex突变ADD_TO_CART

    识别输入和输出

    当您对组件进行单元测试时,您将其视为一个黑盒。方法、计算属性等中的内部逻辑只影响输出。

    所以,下一个重要的事情是确定组件的输入和输出,因为这些也是测试的输入和输出。

    在Item.vue的情况下,输入是:

    • id道具

    • 来自Vuex和Vue Auth的声明

    • 用户通过单击按钮输入

    而输出为:

    • 呈现标记

    • 数据发送到Vuex突变或Vue路由器推送

    有些组件还可以将表单和事件作为输入,并将事件作为输出发出。

    测试1:当客户单击按钮时调用路由器

    一个业务逻辑是“如果用户是访客,单击添加到购物车按钮会将他们重定向到登录页面”。让我们为此写一个测试。

    我们将通过浅层安装组件来设置测试,然后找到并单击Add to Cart按钮。

    test("router called when guest clicks button", () => {
      const config = createConfig();
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      // Assertion goes here
    }

    我们稍后将添加一个断言。

    不要超出输入和输出的边界

    在这个测试中,我们很有可能会在单击按钮后检查路由是否更改为登录页面的路由,例如。

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      // Wrong
      const route = router.find(route => route.name === "login");
      expect(wrapper.vm.$route.path).toBe(route.path);
    }

    虽然这确实隐式地测试了组件输出,但是它依赖于路由器来工作,这不应该是这个组件所关心的。

    最好直接测试这个组件的输出,即对$router.push的调用。路由器是否完成该操作超出了此特定测试的范围。

    因此,让我们监视路由器的push方法,并断言它是用login route对象调用的。

    import router from "router";
    
    test("router called when guest clicks button", () => {
      ...
      jest.spyOn(config.mocks.$router, "push");
      const route = router.find(route => route.name === "login");
      expect(spy).toHaveBeenCalledWith(route);
    }

    测试2:vuex在auth用户单击按钮时调用

    接下来,让我们测试“如果用户已登录,单击Add to Cart按钮将触发Vuex突变ADD_TO_CART”的业务逻辑。

    要重复上面的教训,您不需要检查Vuex状态是否被修改。我们将对Vuex商店进行单独的测试来验证这一点。

    这个组件的工作就是提交,所以我们只需要测试它就可以了。

    首先重写$auth。检查mock,使其返回true(对于已登录的用户也是如此)。然后,我们将监视存储的提交方法,并断言它是在单击按钮后调用的。

    test("vuex called when auth user clicks button", () => {
      const config = createConfig({
        mocks: {
          $auth: {
            check: () => true
          }
        }
      });
      const spy = jest.spyOn(config.mocks.$store, "commit");
      const wrapper = shallowMount(Item, config);
      wrapper
        .find("button")
        .trigger("click");
      expect(spy).toHaveBeenCalled();
    }

    不要测试其他库的功能

    Item组件显示存储项的数据,特别是标题和图像。也许我们应该写一个测试来专门检查这些?例如:

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      // Wrong
      expect(wrapper.find("h2").text()).toBe(item.title);
    }

    这是另一个不必要的测试,因为它只是测试Vue从Vuex中获取数据并将其插入到模板中的能力。Vue库已经对该机制进行了测试,所以您应该依赖于它。

    测试3:正确渲染

    但是如果有人不小心将title重命名为name,然后忘记更新插值表达式怎么办?这难道不值得测试吗?

    是的,但如果你像这样测试模板的每个方面,你会在哪里停止?

    测试标记的最佳方法是使用快照测试来检查整体渲染输出。这不仅包括标题插值,还包括图像,按钮文本,任何类等。

    test("renders correctly", () => {
      const wrapper = shallowMount(Item, createConfig());
      expect(wrapper).toMatchSnapshot();
    });

    下面是一些其他不需要测试的例子:

    • 如果src属性绑定到img元素

    • 如果添加到Vuex存储中的数据与插入的数据相同

    • 如果计算的属性返回正确的项

    • 如果路由器推送重定向到正确的页面

    总结

    我认为这三个相对简单的测试对于这个组件来说足够了。

    除非另有证明,否则在单元测试组件进行测试时需要有良好的思维方式。

    以下是您可以自问的问题:

    • 这是业务逻辑的一部分吗?

    • 这是否直接测试组件的输入和输出?

    • 这是测试我的代码还是第三方代码?

    英文原文地址:https://vuejsdevelopers.com/2019/08/26/vue-what-to-unit-test-components/

     想要了解更多相关知识,可访问 前端学习网站!!

    展开全文
  • 记录一些在为项目引入单元测试时的一些困惑,希望可以对社区的小伙伴们有所启迪,少走一些弯路少踩一些坑。 jest, mocha, karma, chai, sinon, jsmine, vue-test-...如何为聊天的文字消息组件单元测试? 运行...
  • Jest - Vue 组件单元测试 单元测试就是对一个函数的输入和输出进行测试。 使用断言的方式,根据输入判断实际的输出和预测的输出是否相同。 使用单元测试的目的,是用来发现模块内部可能存在的各种错误。 组件的...
  • 本文的目标 2.2 在Vue应用的单元测试中,对 Vuex store 该如何测试?如何测试与 Vue 组件之间的交互? // Given 一个有基本的UT知识和Vue组件单元测试经验的开发者
  • Vue单元测试入门三、UniApp的View组件测试一、UniApp自动化测试快速开始二、上手1. 创建测试项目2. package.json 的 script节点新增节点三、运行测试四、测试用例重点1. 测试用例格式2. `beforeAll`3. 获取 page ...
  • 基于Vue单元测试

    2021-06-21 13:40:06
    Vue 的单文件组件使得为组件撰写隔离的单元测试这件事更加直接。它会让你更有信心地开发新特性而不破坏现有的实现,并帮助其他开发者理解你的组件的作用。 单元测试应该: 可以快速运行 易于理解 只测试一个独立...
  • cypress-vue-unit-test:在Cypress.io E2E测试运行器中对Vue组件进行单元测试的小帮手
  • Vue Test Utils和Jest:如何为Vue组件编写简单的单元测试 (Vue Test Utils and Jest: how to write simple unit tests for Vue components) In this tutorial I’m going to show you how to t...
  • 单元测试Vue组件

    2020-11-05 21:51:30
    在测试套件中使用beforeEach()和afterEach()方法 为Vue组件中的实现细节编写单元测试Vue组件的行为编写单元测试(点击事件等等) 理解为什么mock数据有助于单元测试 为mock库和异步函数编写单元测试 检测单元测试...
  • VUE+jtest的组件单元测试

    千次阅读 2018-01-18 03:15:39
    VUE的脚手架本身自带单元测试功能,可以选择karma和jtest两种,本文主要是测试了两种方式的不同。在讲到前端测试的时候我们需要区分以下两种测试概念: e2e测试:测试一个应用从头到尾的流程是否和设计时候所想的...
  • 最近做了一次关于vue组件自动化测试的分享,现在将vue组件单元测试环境搭建过程整理一下。这次搭建的测试环境和开发环境隔离,所以理论上适用所有使用vue的开发环境。 准备 这篇文章的重点在于搭建测试环境,所以我...
  • 创建一个vue项目单元测试的demo 1、创建新项目vue webpack init unit-test 2、在src/components下创建组件 content.vue <template> <div class="content-wrap"> <h1 class="title">{{ content ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,980
精华内容 3,192
关键字:

vue组件单元测试

vue 订阅