我爱编程

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

2018-08-08  本文已影响0人  Revontulet

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开发的测试框架,有很多清爽简便的特性:

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 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()
      })
    })
上一篇下一篇

猜你喜欢

热点阅读