Part 1: 在Jest框架中设计你的第一则Vue.js单元测试用例

Write the first Vue.js Component Unit Test in Jest

在Jest框架中设计你的第一则Vue.js单元测试用例

Learn how to write unit tests with the official VueJS tools and the Jest framework.

学习如何运用VueJS官方测试工具与Jset框架设计单元测试用例

vue-test-utils, the official VueJS testing library and based on avoriaz, is just around the corner. @EddYerburgh is indeed doing a very good job creating it. It provides all necessary tooling for making easy to write unit test in a VueJS application.

首先,vue-test-utils基于avoriaz,是VueJS官方的测试库,集成了众多测试工具,简化了对VueJs应用进行单元测试的设计。

Jest, on the other side, is the testing framework developed at Facebook, which makes testing a breeze, with awesome features such as:

另外一方面,Jest是Facebook开发的测试框架,有很多清爽简便的特性:

  • Almost no config by default
    • 默认情况基本无需用户配置相关参数
  • Very cool interactive mode
    • 交互模式非常酷
  • Run tests in parallel
    • 对用例并发测试
  • Spies, stubs and mocks out of the box
    • Spies, 可以提供函数调用的信息,但不会改变函数的行为
    • Stubs, 与spies类似,但是会完全替换目标函数。这使得一个被stubbed的函数可以做任何你想要的 —— 例如抛出一个异常,返回某个特定值等等。
    • Mocks, 通过组合spies和stubs,使替换一个完整对象更容易。
  • Built in code coverage
    • 内置可视化覆盖率报告功能
  • Snapshot testing
    • 快照测试功能
  • Module mocking utilities
    • 实用的mocking模块

Probably you’ve already written test without this tools, and just by using karma + mocha + chai + sinon + …, but you’ll see how much easier it can be 😉.

也许你曾经没有用过上述工具,只是用karma、mocha、chai、sinon等设计测试用例,那么接下来你将看到颠覆性的用例设计模式。

Set up a vue-test sample project

搭建一个Vue的测试项目

Let’s start by creating a new project using vue-cli answering NO to all yes/no questions:

我们用vue-cli搭设一个全新的项目,对所有需要您回答“是否需要。。。”的步骤全部选择“不要(NO)”。

    npm install -g vue-cli
    vue init webpack vue-test
    cd vue-test

Then we’ll need to install some dependencies:

然后我们需要安装以下几个必要的依赖:

    # Install dependencies
    npm i -D jest jest-vue-preprocessor babel-jest

jest-vue-preprocessor is needed for making jest understand .vue files, and babel-jest for the integration with Babel.

jest-vue-preprocessor是用来识别并解析VUE组件文件的工具,babel-jest则是用来整合Babel工具库的桥梁。

As vue-test-utils, it can be installed already from npm, since beta.1 has been published.

vue-test-utils已经发布了beta.1版本,现在我们已经可以在npm上安装了。

    npm i -D vue-test-utils

Let’s add the following Jest configuration in the package.json:

我们在package.json文件中添加以下Jest的配置:

    ...
    "jest": {
      "moduleNameMapper": {
        "^vue$": "vue/dist/vue.common.js"
      },
      "moduleFileExtensions": [
        "js",
        "vue"
      ],
      "transform": {
        "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
        ".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
      }
    }
    ...

moduleFileExtensions will tell Jest which extensions to look for, and transform which preprocessor to use for a file extension.

moduleFileExtensions是来告诉Jest扫描哪些类型的文件;transform设定了各个类型的文件对应哪些预处理工具。

At last, add a test script to the package.json:

最后我们添加一段测试脚本在package.jsonscripts里:

    {
      "scripts": {
        "test": "jest",
        ...
      },
      ...
    }

Testing a Component

对一个组件进行测试

I’ll be using Single File Components here, and I haven’t checked if it works by splitting them in their own html, css or js files, so let’s assume you’re doing that as well.

我将会用单文件组件进行测试,我暂时先不管它的html结构、样式和js逻辑是否可行,我先假设你已经把他们都处理好了。

First create a MessageList.vue component under src/components:

首先先创建一个MessageList.vue组件,放在src/components目录下:

    <template>
        <ul>
            <li v-for="message in messages">
                {{ message }}
            </li>
        </ul>
    </template>

    <script>
    export default {
      name: 'list',
      props: ['messages']
    }
    </script>

And update App.vue to use it, as follows:

然后在App.vue中引入MessageList.vue

    <template>
      <div id="app">
        <MessageList :messages="messages"/>
      </div>
    </template>

    <script>
    import MessageList from './components/MessageList'

    export default {
      name: 'app',
      data: () => ({ messages: ['Hey John', 'Howdy Paco'] }),
      components: {
        MessageList
      }
    }
    </script>

We have already a couple of components that we can test. Let’s create a test folder under the project root, and a App.test.js:

我们已经有了一对组件可以进行测试。然后我们在项目根目录下创建一个测试文件夹和一个App.test.js文件:

    import Vue from 'vue'
    import App from '../src/App'

    describe('App.test.js', () => {
      let cmp, vm

      beforeEach(() => {
        cmp = Vue.extend(App) // Create a copy of the original component
        vm = new cmp({
          data: { // Replace data value with this fake data
            messages: ['XXX']
          }
        }).$mount() // Instances and mounts the component
      })

      it('equals messages to ["XXX"]', () => {
        expect(vm.messages).toEqual(['XXX'])
      })
    })

Right now, if we run npm test (or npm t as a shorthand version), the test should run and pass. Since we’re modifying the tests, let’s better run it in watch mode:

现在我们在命令行执行npm test(或短命令npm run t),可以看到用例已经被执行,而且已经通过了测试。如果想观察修改用例会有什么效果,可以执行npm t --watch命令。

    npm t -- --watch

The problem with nested components

测试过程中要注意的嵌套组件问题

This test is too simple. Let’s check that the output is the expected as well. For that we can use the amazing Snapshots feature of Jest, that will generate a snapshot of the output and check it against in the upcoming runs. Add after the previous it in App.test.js:

这个测试有些过于简单,我们来检测一下输出结果是否符合我们的预期。
此时我们可以用Jest神奇的快照功能来进行测试,此功能会产生一个输出结果的快照,与接下来后续测试的结果进行对比。
我们先将一下代码添加到App.test.js中:

    it('has the expected html structure', () => {
      expect(vm.$el).toMatchSnapshot()
    })

That will create a test/snapshots/App.test.js.snap file. Let’s open it and inspect it:

进程会在__snapshots__目录中产生一个App.test.js.snap文件。
我们打开后看到里面的代码是:

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

    exports[`App.test.js has the expected html structure 1`] = `
    <div
      id="app"
    >
      <ul>
        <li>
          XXX
        </li>
      </ul>
    </div>
    `;

In case you haven’t noticed, there is a big problem here: the MessageList component has been rendered as well. Unit tests must be tested as an independent unit, meaning that in App.test.js we wanna test App component and don’t care at all about anything else.

也许你没有发现,这里有个问题——MessageList组件在父组件的快照中已经被成功渲染。

单元测试的理念是单个组件需要被作为独立单元检测,这意味着我们测试App根组件的时候,其实是不在乎其中的子组件具体是什么状态的。

This can be the reason of several problems. Imagine for example, that the children components (MessageList in this case) perform side effect operations on the created hook, such as calling fetch, a Vuex action or state changes? That’s something we definitely don’t want.

这会引起几个相关问题。想象一下,像MessageList这样的子组件,会在created钩子中引发副作用,比如会不会调用一些获取数据的方法、Vuex的action被调用来改变了state中的数据?这些不是我们想要看到的。

Luckily, Shallow Rendering solves this nicely.
幸运的是,浅渲染可以帮我们完美解决问题!

What is Shallow Rendering?

什么是浅渲染

Shallow Rendering is a technique that assures your component is rendering without children. This is useful for:

浅渲染是确保组件不受子组件影响,被独立测试的技术,其用武之地包括:

  • Testing only the component you want to test (that’s what Unit Test stands for)
    • 只测试你想测的组件(这本身就是单元测试的初衷)
  • Avoid side effects that children components can have, such as making HTTP calls, calling store actions…
    • 避免子组件对父组件的影响,如请求HTTP、store中的actions等动作

Testing a Component with vue-test-utils

运用vue-test-utils来对组件测试

vue-test-utils provide us with Shallow Rendering among other features. We could rewrite the previous test as follows:

vue-test-utils 提供给我们一系列有用的特性,其中就包括浅渲染,我们可以改写一下我们之前写的测试用例:

    import { shallow } from 'vue-test-utils'
    import App from '../src/App'

    describe('App.test.js', () => {
      let cmp

      beforeEach(() => {
        cmp = shallow(App, { // Create a shallow instance of the component
          data: {
            messages: ['XXX']
          }
        })
      })

      it('equals messages to ["XXX"]', () => {
        // Within cmp.vm, we can access all Vue instance methods
        expect(cmp.vm.messages).toEqual(['XXX'])
      })

      it('has the expected html structure', () => {
        expect(cmp.element).toMatchSnapshot()
      })
    })

And now, if you’re still running Jest in watching mode, you’ll see the test still pass, but the Snapshot doesn’t match. Press u to regenerate it. Open and inspect it again:

现在,如果你之前执行的是监听模式,你可以看到测试用例依然通过了,但是快照已经报错了,提示不匹配。 你可以在命令行用u参数,来重新生成快照,我们现在看看快照代码的内容变成什么样了:

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

exports[`App.test.js has the expected html structure 1`] = `
<div
  id="app"
>
  <!--  -->
</div>
`;

You see? Now no children have been rendered and we tested the App component fully isolated from the component tree. Also, if you have any created or whatever hooks in the children components, they haven’t been called either 😉.

看到了吗?现在没有子组件被渲染后的代码了。我们现在终于客观上完成了对App组件进行真正意义上的单元测试。此时无论你在子组件中任何生命周期的钩子里有什么样的动作,都不会被包括在此轮的测试中。

If you’re curious about how shallow render is implemented, check out the source code and you’ll see that basically is stubbing the components key, the render method and the lifecycle hooks.

如果你好奇浅渲染的底层逻辑是什么,你可以查看到源码中关于生命周期钩子和相关方法的代码。

In the same vein, you can implement the MessageList.test.js test as follows:

同样的方式,我们可以创建MessageList.test.js文件,来对MessageList进行测试:

    import { shallow } from 'vue-test-utils'
    import MessageList from '../src/components/MessageList'

    describe('MessageList.test.js', () => {
      let cmp

      beforeEach(() => {
        cmp = shallow(MessageList, {
          // Beaware that props is overriden using `propsData`
          propsData: {
            messages: ['XXX']
          }
        })
      })

      it('has received ["XXX"] as the message property', () => {
        expect(cmp.vm.messages).toEqual(['XXX'])
      })

      it('has the expected html structure', () => {
        expect(cmp.element).toMatchSnapshot()
      })
    })
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,251评论 0 10
  • 看到别人画的真人漫画版,不由也想试试,周五和同学的合照,拿来练手,一秒变萌的感觉真不错,哈哈! 朋友们,喜欢这样萌...
    木可人儿阅读 3,830评论 12 10
  • 今天七月一日是市重点中学考试日,女儿要在今天上“战场”。 女儿已走进考场。作为母亲,为她担心着,但什么都做不了,只...
    雪漫飞阅读 298评论 10 5
  • 《天蝎计划》第二季第12集“圣诞溃坝” 二〇一六年十二月二日星期五 在圣诞前夜,一座无人值守的大坝出现了溃坝险情,...
    未来星星阅读 944评论 0 0
  • 《新科学家》| 这样运动更聪明 马徐骏 我们都知道运动有助于保持健康,但是在高速转动的生活里,想要保持运动的好习惯...
    JAMESCAMERONL阅读 407评论 0 2