为了账号安全,请及时绑定邮箱和手机立即绑定

Part 5: 测试Vue组件的Computed和Watchers功能

标签:
Vue.js

Test Computed Properties and Watchers in Vue.js Components with Jest

测试Vue组件的Computed和Watchers功能

Learn about testing Computed Properties and Watchers reactivity in Vue.js.
学习如何测试组件对Computed和Watchers功能的反应。

Computed properties and watchers are reactive parts of the logic of Vue.js components. They both serve totally different purposes, one is synchronous and the other asynchronous, which makes them behave slightly different.
Computed和Watchers功能是vue.js组件的逻辑反应部分。他们被设计的目的不同,一个是同步一个是异步,所以使得他们的行为迥异。

In this article we’ll go through testing them and see what different cases we can find on the way.
这篇文章中我们通过测试Computed和Watchers功能,来发现它们彼此的特性。

Computed Properties

Computed功能

Computed properties are simple reactive functions that return data in another form. They behave exactly like the language standard get/set properties:
Computed功能是一个简单的反应式函数,以另一种形式返回数据。它们的行为就像编程语言中常见的get或set方法。

class X {
  ...

  get fullName() {    return `${this.name} ${this.surname}`
  }

  set fullName() {
    ...
  }
}

In fact, when you’re building class based Vue components, as I explain in my Egghead course “Use TypeScript to Develop Vue.js Web Applications”, you’ll write it just like that. If you’re using plain objects, it’d be:
实际上,当你创建基于Vue组件的Class类的时候,你可以就这么开发代码。如果你使用普通对象方式编辑代码,那么可以如下开发:

export default {
  ...
  computed: {
    fullName() {      return `${this.name} ${this.surname}`
    }
  }
}

And you can even add the set as follows:
还可以继续添加set方法:

computed: {    fullName: {
      get() {        return `${this.name} ${this.surname}`
      },
      set() {
        ...
      }
    }
  }

Testing Computed Properties

测试Computed属性

Testing a computed property is very simple, and probably sometimes you don’t test a computed property exclusively, but test it as part of other tests. But most times it’s good to have a test for it, whether that computed property is cleaning up an input, or combining data, we wanna make sure things work as intended. So let’s begin.
想测试Computed属性非常简单,甚至可能你根本不用专门来测它,只需要在其他测试用例中带上它一起测了。但是通常测试Computed属性还是有必要的,因为我们想知道计算属性是否将input的内容清空了,或成功绑定了数据。下面就是我们的测试方法:

First of all, create a Form.vue component:
首先,创建一个表单组件Form.vue:

<template>
  <div>
    <form action="">
      <input type="text" v-model="inputValue">
      <span class="reversed">{{ reversedInput }}</span>
    </form>
  </div>
</template>

<script>export default {
  props: ['reversed'],
  data: () => ({
    inputValue: ''
  }),
  computed: {    reversedInput() {      return this.reversed ?
        this.inputValue.split("").reverse().join("") :
        this.inputValue
    }
  }
}
</script>

It will show an input, and next to it the same string but reversed. It’s just a silly example, but enough to test it.
这个表单中会显示一个输入框,紧接着会显示反转后的输入内容。这只是个简单至极的例子,但是已经足够应付此类的测试了。

Now add it to App.vue, put it after the MessageList component, and remember to import it and include it within the components component option. Then, create a test/Form.test.js with the usual bare-bones we’ve used in other tests:
现在将此组件添加到App根组件,放到MessageList组件后,别忘了引入并在components属性中注册为子组件。然后在test目录下创建一个Form.test.js文件,只需要在文件中手动引入Form组件。

import { shallow } from 'vue-test-utils'import Form from '../src/components/Form'describe('Form.test.js', () => {  let cmp

  beforeEach(() => {
    cmp = shallow(Form)
  })
})

Now create a test suite with 2 test cases:
现在写一个测试套件,来测以下两个用例:

describe('Properties', () => {
  it('returns the string in normal order if reversed property is not true', () => {
    cmp.vm.inputValue = 'Yoo'
    expect(cmp.vm.reversedInput).toBe('Yoo')
  })

  it('returns the reversed string if reversed property is true', () => {
    cmp.vm.inputValue = 'Yoo'
    cmp.setProps({ reversed: true })
    expect(cmp.vm.reversedInput).toBe('ooY')
  })
})

We can access the component instance within cmp.vm, so we can access the internal state, computed properties and methods. Then, to test it is just about changing the value and making sure it returns the same string when reversed is false.
我们可以用组件实例来访问到cmp.vm对象,所以我们可以直接拿到组件的内部state, computed properties 和 methods属性。然后,为了测试组件,我们只需要改变以下输入框的值,看返回的值是否符合我们的预期——反转开关为false时,字符串并没有被反转。

For the second case, it would be almost the same, with the difference that we must set the reversed property to true. We could navigate through cmp.vm... to change it, but vue-test-utils give us a helper method setProps({ property: value, ... }) that makes it very easy.
对于第二个用例,并没有太大差别,只不过我们必须设置反转开关为true。我们依然可以通过cmp.vm来改变其值,但是vue-test-utils还提供给我们一个更简单实用的方法——setProps({ property: value, ... })

That’s it, depending on the computed property it may need more test cases.
以上用例比较简单,但是基于computed的用例设计远不止于此。

Watchers

Watchers

Honestly, I haven’t come across any case where I really need to use watchers that I computed properties couldn’t solve. I’ve seen them misused as well, leading to a very unclear data workflow among components and messing everything up, so don’t rush on using them and think beforehand.
坦白讲,我实际开发过程中,我没有见过非得用watchers属性,而不能用computed解决的问题。我也见了很多对watchers误用的例子,简直就是将工作流搞得一团糟,所以在用watchers属性之前一定要考虑再三。

As you can see in the Vue.js docs, watchers are often used to react to data changes and perform asynchronous operations, such can be performing an ajax request.
正如你看到Vue的官方文档中说的那样,watchers通常用于对于数据变更后做出响应,并执行异步操作,如此一来就可以执行Ajax请求了。

Testing Watchers

对Watcher进行测试

Let’s say we wanna do something when the inputValue from the state change. We could do an ajax request, but since that’s more complicated and we’ll see it in the next lesson, let’s just do a console.log. Add a watch property to the Form.vue component options:
我们如果想在表单中inputValue数据变化后做些什么,我们可以执行一次Ajax请求,但是这太复杂了,后文再说,现在只做下console.log就可以了。对Form.vue添加一个监测属性:

watch: {
  inputValue(newVal, oldVal) {    if(newVal.trim().length && newVal !== oldVal) {      console.log(newVal)
    }
  }
}

Notice the inputValue watch function matches the state variable name. By convention, Vue will look it up in both properties and data state by using the watch function name, in this case inputValue, and since it will find it in data, it will add the watcher there.
有没有注意到检测的函数名字是inputValue,正好是组件state数据中的变量名?按照惯例,Vue会将监测方法名在属性和组件的State的data中遍历,就像此处的inputValue,因为可以在data中找到,接下来就会被列为监测对象。

See that a watch function takes the new value as a first parameter, and the old one as the second. In this case we’ve chosen to log only when it’s not empty and the values are different. Usually, we’d like to write a test for each case, depending on the time you have and how critical that code is.
在监测函数的参数中第一个是新值,旧值在第二处。我们当前选择只有在输入框值不为空或者被改写的时候才打印出值,通常,我们希望为每个用例编写一个测试,当然,这取决于你有余时以及待测试的代码有多关键。

What should we test about the watch function? Well, that’s something we’ll also discuss further in the next lesson when we talk about testing methods, but let’s say we just wanna know that it calls the console.log when it should. So, let’s add the bare bones of the watchers test suite, within Form.test.js:
那么关于Watch的功能,我们可以测哪些方面呢?这也留在后文,与methods属性一起讲。现在我们只是想知道它符合我们的预期——调用了console.log方法。所以,我们添加如下代码:

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

  describe('Watchers - inputValue', () => {    let spy

    beforeAll(() => {
      spy = jest.spyOn(console, 'log')
    })

    afterEach(() => {
      spy.mockClear()
    })

    it('is not called if value is empty (trimmed)', () => {
    })

    it('is not called if values are the same', () => {
    })

    it('is called with the new value in other cases', () => {
    })
  })
})

We’re using a spy on the console.log method, initializing before starting any test, and resetting its state after each of them, so that they start from a clean spy.
我们对console.log进行暗地窥测,在每一个测试之前先进行初始化,测试完以后再进行恢复,这样,每次测试都是一个干净、无数据污染的spy。

To test a watch function, we just need to change the value of what’s being watch, in this case the inputValue state. But there is something curious… let’s start by the last test
要测试一个监测方法,我们只需要更改一下我们监控下的数据值,即此处的inputValue变量。但是现在有一件稀奇事:

it('is called with the new value in other cases', () => {
  cmp.vm.inputValue = 'foo'
  expect(spy).toBeCalled()
})

We change the inputValue, so the console.log spy should be called, right? Well, if you run it, you’ll notice that is not! WTF??? Wait, there is an explanation for this: unlike computed properties, watchers are deferred to the next update cycle that Vue uses to look for changes. So, basically, what’s happening here is that console.log is indeed called, but after the test has finished.
我们改变了inputValue,按理监测的console.log是应该被调用的,但是事实呢?我们可以看到运行测试后并没有得到预期效果。这是为什么呢?好吧,这是因为跟计算属性不同,监控属性被推迟在下一个vue寻找数据变化的更新周期中。所以,总的说来,console.log方法确实被调用了,但是被推迟到了测试代码结束后。

To solve this, we need to use the vm.nextTick function to defer code to the next update cycle. But if we write: 解决这个问题我们需要用到`vm.nextTick`方法,此方法可以推迟代码到下一个更新周期,但是如果我们如下所写:

it('is called with the new value in other cases', () => {
  cmp.vm.inputValue = 'foo'
  cmp.vm.$nextTick(() => {
    expect(spy).toBeCalled()
  })
})

It will still fail, since the test finishes with the expect function not being called. That happens because now is asynchronous and happens on the nextTick callback. How can we then test it if the expect happens at a later time? 测试用例依然会被断言失败,这是因为测试结束后,断言表达式依然没有被调用。这类情况发生是因为如今代码执行在异步队列中,会在回调`nextTick`中被执行。我们怎么才能成功测试到后续才会执行的代码呢?

Jest give us a next parameter that we can use in the it test callbacks, in a way that if it is present, the test will not finish until next is called, but if it’s not, it will finish synchronously. So, to finally get it right:
Jest给我们传了一个next参数,我们可以用它在测试用例中测试回调,原理就是如果next存在,那么测试就会将它之前的代码完全执行完毕,执行next()后再结束测试。但是如果next不存在,那么测试会被同步执行。终于,我们如愿以偿了:

it('is called with the new value in other cases', next => {
  cmp.vm.inputValue = 'foo'
  cmp.vm.$nextTick(() => {
    expect(spy).toBeCalled()
    next()
  })
})

We can apply the same strategy for the other two, with the difference that the spy shouldn’t be called:
我们可以对其他两个用例用同样的方法,代码略有不同:

it('is not called if value is empty (trimmed)', next => {
  cmp.vm.inputValue = '   '
  cmp.vm.$nextTick(() => {
    expect(spy).not.toBeCalled()
    next()
  })
})

it('is not called if values are the same', next => {
  cmp.vm.inputValue = 'foo'

  cmp.vm.$nextTick(() => {
    spy.mockClear()
    cmp.vm.inputValue = 'foo'

    cmp.vm.$nextTick(() => {
      expect(spy).not.toBeCalled()
      next()
    })
  })
})

That second one gets a bit more complex than it looked like. The default internal state is empty, so first we need to change it, wait for the next tick, then clear the mock to reset the call count, and change it again. Then after the second tick, we can check the spy and finish the test.
第二个用例其实远比表面的代码要复杂。组件内部数据的默认值是空,所以首先我们需要改变它,等待下一个更新周期,然后清除模拟的mock、对调用复位,然后再改变它。之后的一个更新周期,我们就可以检查窥测的对象并完成测试了。

This can get simpler if we recreate the component at the beginning, overriding the data property. Remember we can override any component option by using the second parameter of the mount or shallow functions:
这样如果我们在开头部分重复创建组件实例,改写data属性时会更简便。请留心一件事——我们可以用mount或shallow函数的第二个参数修改任意组件选项。

it('is not called if values are the same', next => {
  cmp = shallow(Form, { data: ({ inputValue: 'foo' }) })
  cmp.vm.inputValue = 'foo'

  cmp.vm.$nextTick(() => {
    expect(spy).not.toBeCalled()
    next()
  })
})

Conclusion

总结

You’ve learned in this article how to test part of the logic of Vue components: computed properties and watchers. We’ve gone through different test cases we can come across testing them. Probably you’ve also learned some of the Vue internals such as the nextTick update cycles.
大家在此文中已经学到了如何测试Vue组件的一部分逻辑代码——计算属性和监测属性。我们也尽量多地覆盖测试面。希望您已经学会了一些Vue的基础知识,比如nextTick的更新周期。



作者:Revontulet
链接:https://www.jianshu.com/p/e12373863cb4


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 1
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消