资讯专栏INFORMATION COLUMN

在Vue项目中使用snapshot测试

blair / 3053人阅读

摘要:在项目中使用测试介绍测试又称快照测试,可以直观地反映出组件是否发生了未预见到的变化。结合进行测试生成快照时需要渲染并挂载组件,在中可以使用官方的单元测试实用工具。

在Vue项目中使用snapshot测试 snapshot介绍

snapshot测试又称快照测试,可以直观地反映出组件UI是否发生了未预见到的变化。snapshot如字面上所示,直观描述出组件的样子。通过对比前后的快照,可以很快找出UI的变化之处。

第一次运行快照测试时会生成一个快照文件。之后每次执行测试的时候,会生成一个快照,然后对比最初生成的快照文件,如果没有发生改变,则通过测试。否则测试不通过,同时会输出结果,对比不匹配的地方。

jest中的快照文件以为snap拓展名结尾,格式如下(ps: 在没有了解之前,我还以为是快照文件是截图)。一个快照文件中可以包含多个快照,快照的格式其实是HTML字符串,对于UI组件,其HTML会反映出其内部的state。每次测试只需要对比字符串是否符合初始快照即可。

exports[`button 1`] = `"
1
"`;

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组件的代码和测试。




进行快照测试时,除了测试数据渲染是否正确外还可以模拟事件。这里只贴快照测试用例的代码,完整的代码戳我。

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`] = `
  • test TodoItem
  • `; exports[`TodoItem snapshot test mouseover 2`] = `
  • test TodoItem
  • `; exports[`TodoItem snapshot test toggle checked 1`] = `
  • test TodoItem
  • `; exports[`TodoItem snapshot test toggle checked 2`] = `
  • test TodoItem
  • `;

    这里使用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`] = `
  • test TodoItem
  • `; exports[`TodoItem snapshot test mouseover 1`] = `
  • test TodoItem
  • `; exports[`TodoItem snapshot test toggle checked 1`] = `
  • test TodoItem
  • `;

    第二次执行测试后,输出中有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
    
        - 
  • test TodoItem
  • +
  • test TodoItem content change
  • 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返回处理后的结果。

    后记

    在未了解测试之前,我一直以为测试是枯燥无聊的。了解过快照测试后,我发现测试其实蛮有趣且实用,同时由衷地感叹快照测试的巧妙之处。如果这个简单的案例能让你了解快照测试的作用及使用方法,就是我最大的收获。

    如果有问题或错误之处,欢迎指出交流。

    参考链接

    vue-test-utils-jest-example

    Jest - Snapshot Testing

    Vue Test Utils

    Vue SSR 指南

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/100522.html

    相关文章

    • 10秒钟构建你自己的”造轮子”工厂! 2019年github/npm工程化协作开发栈最佳实践

      摘要:年工程化协作开发栈最佳实践我们将花半小时实战撸一个包含,的标准的用于工程协作的包开发栈。使用脚手架,秒钟构建可自由配置的开发栈。分别表示询问弹窗自动执行任务执行任务后操作。 发起一个github/npm工程协作项目,门槛太高了!! 最基础的问题,你都要花很久去研究: 如何在项目中全线使用es2017代码? 答案是babel 如何统一所有协作者的代码风格? 答案是eslint + pr...

      dongfangyiyu 评论0 收藏0
    • 使用Maven管理Java项目

      摘要:添加的的依赖,可以从项目的中拷贝使用命令编译项目。在子模块中我们可以这样使用引入父模块坐标三使用建立项目新建一个项目,选择,如图所示在中配置插件可参见官方文档。部署项目到即可。 一、Maven入门 1.下载maven Maven是基于项目对象模型(Project Object Model),可以通过一小段描述信息来管理项目的构建、报告和文档的项目管理工具,提供了一个仓库的概念,统一...

      yiliang 评论0 收藏0
    • SpringBoot-vue 基于Java的微服务全栈快速开发实践

      摘要:本项目将使用配合最简单的逻辑来展示一个基于的微服务全栈快速开发实践的。提供一系列大型项目常用的非功能性特征,比如内嵌服务器,安全,指标,健康检测,外部化配置。 SprintBoot-Vue SpringBoot + 前端MVVM 基于Java的微服务全栈快速开发实践 showImg(https://segmentfault.com/img/remote/1460000010167913...

      FleyX 评论0 收藏0
    • SpringBoot-vue 基于Java的微服务全栈快速开发实践

      摘要:本项目将使用配合最简单的逻辑来展示一个基于的微服务全栈快速开发实践的。提供一系列大型项目常用的非功能性特征,比如内嵌服务器,安全,指标,健康检测,外部化配置。 SprintBoot-Vue SpringBoot + 前端MVVM 基于Java的微服务全栈快速开发实践 showImg(https://segmentfault.com/img/remote/1460000010167913...

      neu 评论0 收藏0
    • 前端每周清单第 10 期:Firefox53、React VR发布、Microsoft Edge现代

      摘要:新闻热点国内国外,前端最新动态发布近日,正式发布新版本中提供了一系列的特性与问题修复。而近日正式发布,其能够帮助开发者快速构建应用。 前端每周清单第 10 期:Firefox53、React VR发布、JS测试技术概述、Microsoft Edge现代DOM树构建及性能之道 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门...

      MingjunYang 评论0 收藏0

    发表评论

    0条评论

    blair

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <