在Vue项目中运用snapshot测试

在Vue项目中运用snapshot测试

snapshot引见

snapshot测试又称快照测试,能够直观地反映出组件UI是不是发生了未预感到的变化。snapshot如字面上所示,直观形貌出组件的模样。经由过程对比前后的快照,能够很快找出UI的变化的处所。

第一次运转快照测试时会天生一个快照文件。以后每次实行测试的时刻,会天生一个快照,然后对比最初天生的快照文件,假如没有发生转变,则经由过程测试。不然测试不经由过程,同时会输出结果,对比不婚配的处所。

jest中的快照文件认为snap拓展名末端,花样以下(ps: 在没有相识之前,我还认为是快照文件是截图)。一个快照文件中能够包含多个快照,快照的花样实际上是HTML字符串,关于UI组件,其HTML会反映出其内部的state。每次测试只须要对比字符串是不是相符初始快照即可。

exports[`button 1`] = `"<div><span class=\\"count\\">1</span> <button>Increment</button> <button class=\\"desc\\">Descrement</button> <button class=\\"custom\\">not emitted</button></div>"`;

snapshot测试不经由过程的缘由有两个。一个缘由是组件发生了不曾预感的变化,此时应搜检代码。另一个缘由是组件更新而快照文件并没有更新,此时要运转jest -u更新快照。

› 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with
-u to update them.

连系Vue举行snapshot测试

天生快照时须要衬着并挂载组件,在Vue中能够运用官方的单元测试有用工具Vue Test Utils

Vue Test Utils 供应了mountshallowMount这两个要领,用于建立一个包含被挂载和衬着的 Vue 组件的 Wrapper。component是一个vue组件,options是实例化Vue时的设置,包含挂载选项和其他选项(非挂载选项,会将它们经由过程extend覆写到其组件选项),结果返回一个包含了一个挂载组件或 vnode,以及测试该组件或 vnode 的要领的Wrapper实例。

mount(component:{Component}, options:{Object})

shallowMountmount差别的是被存根的子组件,细致请戳文档

Wrapper上的雄厚的属性和要领,足以敷衍本文中的测试需求。html()要领返回Wrapper DOM 节点的 HTML 字符串。find()findAll()能够查找Wrapper里的DOM节点或Vue组件,可用于查找监听事宜的元素。trigger能够在DOM节点/组件上触发一个事宜。

连系上述的要领,我们能够完成一个模仿事宜触发的快照测试。

仔细的读者可能会发明,我们日常平凡在运用Vue时,数据更新后视图并不会马上更新,须要在nextTick回调中处置惩罚更新完成后的使命。但在 Vue Test Utils 中,为简化用法,更新是同步的,所以无需在测试中运用 Vue.nextTick 来守候 DOM 更新。

demo演示

Vue Test Utils官方文档中供应了一个集成VTU和Jest的demo,不过这个demo比较旧,官方引荐用CLI3建立项目。

实行vue create vue-snapshot-demo建立demo项目,建立时要挑选单元测试,供应的库有Mocha + ChaiJest,在这里挑选Jest.装置完成以后运转npm run serve即可运转项目。

本文中将用一个简朴的Todo运用项目来演示。这个Todo运用有简朴的增加、删除和修正Todo项状况的功用;Todo项的状况有已完成和未完成,已完成时不可删除,未完成时可删除;已完成的Todo项会用一条线横贯文本,未完成项会在鼠标悬浮时展现删除按钮。

组件简朴地划分为Todo和TodoItem。TodoItem在Todo项未完成且触发mouseover事宜时会展现删除按钮,触发mouseleave时则隐蔽按钮(如许能够在快照测试中模仿事宜)。TodoItem中有一个checkbox,用于切换Todo项的状况。Todo项完成时会有一个todo-finished类,用于完成删除线结果。

为轻易这里只引见TodoItem组件的代码和测试。

<template>
  <li
    :class="['todo-item', item.finished?'todo-finished':'']"
    @mouseover="handleItemMouseIn"
    @mouseleave="handleItemMouseLeave"
  >
    <input type="checkbox" v-model="item.finished">
    <span class="content">{{item.content}}</span>
    <button class="del-btn" v-show="!item.finished&&hover" @click="emitDelete">delete</button>
  </li>
</template>

<script>
export default {
  name: "TodoItem",
  props: {
    item: Object
  },
  data() {
    return {
      hover: false
    };
  },
  methods: {
    handleItemMouseIn() {
      this.hover = true;
    },
    handleItemMouseLeave() {
      this.hover = false;
    },
    emitDelete() {
      this.$emit("delete");
    }
  }
};
</script>
<style lang="scss">
.todo-item {
  list-style: none;
  padding: 4px 16px;
  height: 22px;
  line-height: 22px;
  .content {
    margin-left: 16px;
  }
  .del-btn {
    margin-left: 16px;
  }
  &.todo-finished {
    text-decoration: line-through;
  }
}
</style>

举行快照测试时,除了测试数据衬着是不是准确外还能够模仿事宜。这里只贴快照测试用例的代码,完全的代码戳我

describe('TodoItem snapshot test', () => {
    it('first render', () => {
        const wrapper = shallowMount(TodoItem, {
            propsData: {
                item: {
                    finished: true,
                    content: 'test TodoItem'
                }
            }
        })
        expect(wrapper.html()).toMatchSnapshot()
    })

    it('toggle checked', () => {
        const renderer = createRenderer();
        const wrapper = shallowMount(TodoItem, {
            propsData: {
                item: {
                    finished: true,
                    content: 'test TodoItem'
                }
            }
        })
        const checkbox = wrapper.find('input');
        checkbox.trigger('click');
        renderer.renderToString(wrapper.vm, (err, str) => {
            expect(str).toMatchSnapshot()
        })
    })
    
    it('mouseover', () => {
        const renderer = createRenderer();
        const wrapper = shallowMount(TodoItem, {
            propsData: {
                item: {
                    finished: false,
                    content: 'test TodoItem'
                }
            }
        })
        wrapper.trigger('mouseover');
        renderer.renderToString(wrapper.vm, (err, str) => {
            expect(str).toMatchSnapshot()
        })
    })
})

这里有三个测试。第二个测试模仿checkbox点击,将Todo项从已完成切换到未完成,期待类todo-finished会被移除。第三个测试在未完成Todo项上模仿鼠标悬浮,触发mouseover事宜,期待删除按钮会展现。

这里运用toMatchSnapshot()来举行婚配快照。这里天生快照文件所需的HTML字符串有wrapper.html()Renderer.renderToString这两种体式格局,区分在于前者是同步猎取,后者是异步猎取。

测试模仿事宜时,最好以异步体式格局猎取HTML字符串。同步体式格局猎取的字符串并不一定是UI更新后的视图。

只管VTU文档中说一切的更新都是同步,但实际上在第二个快照测试中,假如运用expect(wrapper.html()).toMatchSnapshot(),天生的快照文件中Todo项仍有类todo-finished,期待的结果应该是没有类todo-finished,结果并不是更新后的视图。而在第三个快照测试中,运用expect(wrapper.html()).toMatchSnapshot()天生的快照,按钮如希冀展现,是UI更新后的视图。所以才不发起在DOM更新的情况下运用wrapper.html()猎取HTML字符串。

下面是两种对比的结果,1是运用wrapper.html()天生的快照,2是运用Renderer.renderToString天生的。

exports[`TodoItem snapshot test mouseover 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="">delete</button></li>`;

exports[`TodoItem snapshot test mouseover 2`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 1`] = `<li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 2`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display:none;">delete</button></li>`;

这里运用vue-server-renderer供应的createRenderer来天生一个Renderer实例,实例要领renderToString来猎取HTML字符串。这类是典范的回调作风,断言语句在回调中实行即可。

    // ...
    wrapper.trigger('mouseover');
    renderer.renderToString(wrapper.vm, (err, str) => {
        expect(str).toMatchSnapshot()
    })

假如不想运用这个库,也能够运用VTU中供应的异步案例。因为wrapper.html()是同步猎取,所以猎取操纵及断言语句须要在Vue.nextTick()返回的Promise中实行。

    // ...
    wrapper.trigger('mouseover');
    Vue.nextTick().then(()=>{
        expect(wrapper.html()).toMatchSnapshot()
    })

视察测试结果

实行npm run test:unityarn test:unit运转测试。

首次实行,终端输出会有Snapshots: 3 written, 3 total这一行,示意新增三个快照测试,并天生初始快照文件。

 › 3 snapshots written.
Snapshot Summary
 › 3 snapshots written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   3 written, 3 total
Time:        2.012s
Ran all test suites.
Done in 3.13s.

快照文件以下示:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TodoItem snapshot test first render 1`] = `<li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>`;

exports[`TodoItem snapshot test mouseover 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn">delete</button></li>`;

exports[`TodoItem snapshot test toggle checked 1`] = `<li class="todo-item"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display:none;">delete</button></li>`;

第二次实行测试后,输出中有Snapshots: 3 passed, 3 total,示意有三个快照测试胜利经由过程,总共有三个快照测试。

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   3 passed, 3 total
Time:        2s
Ran all test suites.
Done in 3.11s.

修正第一个快照中传入的content,从新运转测试时,终端会输出不婚配的处所,输出数据的花样与Git相似,会标明哪一行是新增的,哪一行是被删除的,并提示不婚配代码所在行。

    - Snapshot
    + Received

    - <li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem</span> <button class="del-btn" style="display: none;">delete</button></li>
    + <li class="todo-item todo-finished"><input type="checkbox"> <span class="content">test TodoItem content change</span> <button class="del-btn" style="display: none;">delete</button></li>

      88 |             }
      89 |         })
    > 90 |         expect(wrapper.html()).toMatchSnapshot()
         |                                ^
      91 |     })
      92 |
      93 |     it('toggle checked', () => {

      at Object.toMatchSnapshot (tests/unit/TodoItem.spec.js:90:32)

同时会提示你搜检代码是不是毛病或从新运转测试并供应参数-u以更新快照文件。

Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or re-run jest with `-u` to update them.

实行npm run test:unit -- -uyarn test:unit -u更新快照,输出以下示,能够发明有一个快照测试的输出更新了。下次快照测试对比的文件是这个更新后的文件。

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   1 updated, 2 passed, 3 total
Time:        2.104s, estimated 3s
Ran all test suites.
Done in 2.93s.

其他

除了运用toMatchSnapshot()外,还能够运用toMatchInlineSnapshot()。两者差别的处所在于toMatchSnapshot()从快照文件中查找快照,而toMatchInlineSnapshot()则将传入的参数当做快照文件举行婚配。

设置Jest

Jest设置能够保存在jest.config.js文件里,能够保存在package.json里,用键名jest示意,同时也许可行内设置。

引见几个经常使用的设置。

rootDir

查找Jest设置的目次,默许是pwd

testMatch

jest查找测试文件的婚配划定规矩,默许是[ "**/__tests__/**/*.js?(x)", "**/?(*.)+(spec|test).js?(x)" ]。默许查找在__test__文件夹中的js/jsx文件和以.test/.spec末端的js/jsx文件,同时包含test.jsspec.js

snapshotSerializers

天生的快照文件中HTML文本没有换行,是不是能举行换行美化呢?答案是一定的。

能够在设置中增加snapshotSerializers,接收一个数组,能够对婚配的快照文件做处置惩罚。jest-serializer-vue这个库做的就是如许使命。

假如你想要完成这个本身的序列化使命,须要完成的要领有testprinttest用于挑选处置惩罚的快照,print返回处置惩罚后的结果。

跋文

在未相识测试之前,我一向认为测试是死板无聊的。相识过快照测试后,我发明测试实在蛮风趣且有用,同时由衷地叹息快照测试的奇妙的处所。假如这个简朴的案例能让你相识快照测试的作用及运用要领,就是我最大的收成。

假如有题目或毛病的处所,迎接指出交换。

参考链接

    原文作者:荡漾
    原文地址: https://segmentfault.com/a/1190000017560944
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞