Part 5: 測試Vue組件的Computed和Watchers功能

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功能的反應(yīng)氯哮。

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組件的邏輯反應(yīng)部分棚亩。他們被設(shè)計的目的不同婴渡,一個是同步一個是異步,所以使得他們的行為迥異。

In this article we’ll go through testing them and see what different cases we can find on the way.
這篇文章中我們通過測試Computed和Watchers功能立磁,來發(fā)現(xiàn)它們彼此的特性。

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功能是一個簡單的反應(yīng)式函數(shù),以另一種形式返回數(shù)據(jù)佑吝。它們的行為就像編程語言中常見的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:
實際上绳匀,當你創(chuàng)建基于Vue組件的Class類的時候芋忿,你可以就這么開發(fā)代碼炸客。如果你使用普通對象方式編輯代碼,那么可以如下開發(fā):

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

And you can even add the set as follows:
還可以繼續(xù)添加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的內(nèi)容清空了开仰,或成功綁定了數(shù)據(jù)。下面就是我們的測試方法:

First of all, create a Form.vue component:
首先宣渗,創(chuàng)建一個表單組件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.
這個表單中會顯示一個輸入框抖所,緊接著會顯示反轉(zhuǎn)后的輸入內(nèi)容。這只是個簡單至極的例子痕囱,但是已經(jīng)足夠應(yīng)付此類的測試了田轧。

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:
現(xiàn)在將此組件添加到App根組件,放到MessageList組件后鞍恢,別忘了引入并在components屬性中注冊為子組件傻粘。然后在test目錄下創(chuàng)建一個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:
現(xiàn)在寫一個測試套件弦悉,來測以下兩個用例:

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對象,所以我們可以直接拿到組件的內(nèi)部state, computed properties 和 methods屬性蟆炊。然后稽莉,為了測試組件,我們只需要改變以下輸入框的值涩搓,看返回的值是否符合我們的預(yù)期——反轉(zhuǎn)開關(guān)為false時污秆,字符串并沒有被反轉(zhuǎn)。

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.
對于第二個用例昧甘,并沒有太大差別良拼,只不過我們必須設(shè)置反轉(zhuǎn)開關(guān)為true。我們依然可以通過cmp.vm來改變其值充边,但是vue-test-utils還提供給我們一個更簡單實用的方法——setProps({ property: value, ... })

That’s it, depending on the computed property it may need more test cases.
以上用例比較簡單庸推,但是基于computed的用例設(shè)計遠不止于此。

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.
坦白講浇冰,我實際開發(fā)過程中贬媒,我沒有見過非得用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通常用于對于數(shù)據(jù)變更后做出響應(yīng),并執(zhí)行異步操作仅仆,如此一來就可以執(zhí)行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:
我們?nèi)绻朐诒韱沃?code>inputValue數(shù)據(jù)變化后做些什么,我們可以執(zhí)行一次Ajax請求墓拜,但是這太復(fù)雜了港柜,后文再說,現(xiàn)在只做下console.log就可以了咳榜。對Form.vue添加一個監(jiān)測屬性:

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.
有沒有注意到檢測的函數(shù)名字是inputValue夏醉,正好是組件state數(shù)據(jù)中的變量名?按照慣例涌韩,Vue會將監(jiān)測方法名在屬性和組件的State的data中遍歷畔柔,就像此處的inputValue,因為可以在data中找到臣樱,接下來就會被列為監(jiān)測對象靶擦。

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.
在監(jiān)測函數(shù)的參數(shù)中第一個是新值,舊值在第二處雇毫。我們當前選擇只有在輸入框值不為空或者被改寫的時候才打印出值玄捕,通常,我們希望為每個用例編寫一個測試棚放,當然枚粘,這取決于你有余時以及待測試的代碼有多關(guān)鍵。

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:
那么關(guān)于Watch的功能飘蚯,我們可以測哪些方面呢馍迄?這也留在后文,與methods屬性一起講⌒⒚埃現(xiàn)在我們只是想知道它符合我們的預(yù)期——調(diào)用了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進行暗地窺測庄涡,在每一個測試之前先進行初始化量承,測試完以后再進行恢復(fù),這樣穴店,每次測試都是一個干凈撕捍、無數(shù)據(jù)污染的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
要測試一個監(jiān)測方法泣洞,我們只需要更改一下我們監(jiān)控下的數(shù)據(jù)值忧风,即此處的inputValue變量。但是現(xiàn)在有一件稀奇事:

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球凰,按理監(jiān)測的console.log是應(yīng)該被調(diào)用的狮腿,但是事實呢腿宰?我們可以看到運行測試后并沒有得到預(yù)期效果。這是為什么呢缘厢?好吧吃度,這是因為跟計算屬性不同,監(jiān)控屬性被推遲在下一個vue尋找數(shù)據(jù)變化的更新周期中贴硫。所以椿每,總的說來,console.log方法確實被調(diào)用了英遭,但是被推遲到了測試代碼結(jié)束后间护。

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`方法,此方法可以推遲代碼到下一個更新周期挖诸,但是如果我們?nèi)缦滤鶎懀?/p>

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? 測試用例依然會被斷言失敗汁尺,這是因為測試結(jié)束后,斷言表達式依然沒有被調(diào)用多律。這類情況發(fā)生是因為如今代碼執(zhí)行在異步隊列中均函,會在回調(diào)`nextTick`中被執(zhí)行。我們怎么才能成功測試到后續(xù)才會執(zhí)行的代碼呢菱涤?

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參數(shù)苞也,我們可以用它在測試用例中測試回調(diào),原理就是如果next存在粘秆,那么測試就會將它之前的代碼完全執(zhí)行完畢如迟,執(zhí)行next()后再結(jié)束測試。但是如果next不存在攻走,那么測試會被同步執(zhí)行殷勘。終于,我們?nèi)缭敢詢斄耍?/p>

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.
第二個用例其實遠比表面的代碼要復(fù)雜玲销。組件內(nèi)部數(shù)據(jù)的默認值是空,所以首先我們需要改變它摘符,等待下一個更新周期贤斜,然后清除模擬的mock、對調(diào)用復(fù)位逛裤,然后再改變它瘩绒。之后的一個更新周期,我們就可以檢查窺測的對象并完成測試了带族。

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:
這樣如果我們在開頭部分重復(fù)創(chuàng)建組件實例锁荔,改寫data屬性時會更簡便。請留心一件事——我們可以用mount或shallow函數(shù)的第二個參數(shù)修改任意組件選項蝙砌。

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

總結(jié)

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.
大家在此文中已經(jīng)學到了如何測試Vue組件的一部分邏輯代碼——計算屬性和監(jiān)測屬性阳堕。我們也盡量多地覆蓋測試面跋理。希望您已經(jīng)學會了一些Vue的基礎(chǔ)知識,比如nextTick的更新周期恬总。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薪介,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子越驻,更是在濱河造成了極大的恐慌,老刑警劉巖道偷,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缀旁,死亡現(xiàn)場離奇詭異,居然都是意外死亡勺鸦,警方通過查閱死者的電腦和手機并巍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來换途,“玉大人懊渡,你說我怎么就攤上這事【猓” “怎么了剃执?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懈息。 經(jīng)常有香客問我肾档,道長,這世上最難降的妖魔是什么辫继? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任怒见,我火速辦了婚禮,結(jié)果婚禮上姑宽,老公的妹妹穿的比我還像新娘遣耍。我一直安慰自己,他們只是感情好炮车,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布舵变。 她就那樣靜靜地躺著,像睡著了一般瘦穆。 火紅的嫁衣襯著肌膚如雪棋傍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天难审,我揣著相機與錄音瘫拣,去河邊找鬼。 笑死告喊,一個胖子當著我的面吹牛麸拄,可吹牛的內(nèi)容都是我干的派昧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拢切,長吁一口氣:“原來是場噩夢啊……” “哼蒂萎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淮椰,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤五慈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后主穗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻拦,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年忽媒,在試婚紗的時候發(fā)現(xiàn)自己被綠了争拐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡晦雨,死狀恐怖架曹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闹瞧,我是刑警寧澤绑雄,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站奥邮,受9級特大地震影響轮纫,放射性物質(zhì)發(fā)生泄漏戳吝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望左医。 院中可真熱鬧牙言,春花似錦艘儒、人聲如沸袜漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厕宗。三九已至,卻和暖如春堕担,著一層夾襖步出監(jiān)牢的瞬間已慢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工霹购, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佑惠,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像膜楷,于是被迫代替她去往敵國和親旭咽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,322評論 0 10
  • 梨落千堆雪赌厅,花從繞蝴蝶穷绵,念去去,枯了藤樹特愿,倦了寒鴉仲墨,我在杭城等京城。 ...
    七叔80s閱讀 389評論 0 1
  • 悶熱揍障,宣示著一場雨即將到來目养。 站窗前,見黃昏正洇開亚兄,許多事物已變得模糊。思考著一些事情采驻,沉浸在仆仆的往事里审胚,不覺。...
    走過吳橋閱讀 537評論 16 23
  • 起床礼旅,刷牙洗臉化妝出門 6點45分膳叨,高興 地鐵,讀地藏經(jīng)痘系,復(fù)盤 7點42分 任務(wù)完成菲嘴,高興 到單位,9點02汰翠,顧...
    佑媽帶娃閱讀 129評論 0 0
  • 大學畢業(yè)后龄坪,我來到一家催債公司工作,工作內(nèi)容是罵人和對罵复唤,要求我不能有絲毫的同情心和禮貌健田。 一 2016年大學畢業(yè)...
    真實故事計劃閱讀 2,157評論 12 25