Part 4: 運用Jest測試Vue組件中的繼承屬性和自定義事件

Test Properties and Custom Events in Vue.js Components with Jest

運用Jest測試Vue組件中的繼承屬性和自定義事件

There are different ways to test properties, events and custom events.
測試Vue組件中的繼承屬性膏萧、事件和自定義事件有很多種方法枫耳。

Properties are custom attributes passed from parent to child components. Custom events solve just the opposite, they send data out to the direct parent via an event. They both combined are the wires of interaction and communication in Vue.js components.
繼承屬性是由父組件傳遞進子組件的屬性數(shù)據(jù)回还。自定義事件恰恰相反,是子組件通過一個事件將屬性數(shù)據(jù)傳遞給直接父組件。他們都將Vue的組件聯(lián)系起來歉闰,達到數(shù)據(jù)傳遞的橋梁作用犀被。

In Unit Testing, testing the in and outs (properties and custom events) means to test how a component behaves when it receives and sends out data in isolation. Let’s get our hands dirty!
在單元測試的范疇,測試數(shù)據(jù)接收與冒出(繼承屬性和自定義事件)意味著是測試組件在孤立的情況下寡壮,接收到和冒出數(shù)據(jù)時如何表現(xiàn)。

Properties

繼承屬性

When we are testing component properties, we can test how the component behave when we pass them certain properties. But before going on, an important note:
當我們測試組件的繼承屬性時讹弯,我們可以模擬給組件傳遞一個確定的數(shù)據(jù)屬性况既,測試組件的行為表現(xiàn)。開工之前组民,給您一個重要提示:

To pass properties to components, use propsData, and not props. The last one is to define properties, not to pass them data.
給組件傳遞數(shù)據(jù)棒仍,可以用propsData,而不是正常組件中用的props臭胜,后者是用來定義屬性的莫其,而不是用來給屬性傳遞數(shù)據(jù)癞尚。

First create a Message.test.js file and add the following code:
首先創(chuàng)建一個Message.test.js文件,代碼如下:

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

  describe('Properties', () => {
    // @TODO
  })
})

We group test cases within a describe expression, and they can be nested. So we can use this strategy to group the tests for properties and events separately.
我們可以將測試用例歸集到一個describe表達式里乱陡,且describe還可以嵌套describe浇揩。如此一來,我們就可以使用這個語法規(guī)則憨颠,將測試繼承屬性和自定義事件兩組用例分隔成組胳徽。

Then we’ll create a helper factory function to create a message component, give some properties
然后我們來創(chuàng)建一個函數(shù)來幫助我們按需生成組件實例,并且傳入一些屬性爽彤。

const createCmp = propsData => mount(Message, { propsData })

Testing property existence

測試屬性的存在與否

Two obvious things we can test is that a property exists, or it doesn’t. Remember that the Message.vue component has a message property, so let’s assert that it receives correctly that property. vue-test-utils comes with a hasProp(prop, value) function, which is very handy for this case:
很明顯养盗,我們是可以測試一個數(shù)據(jù)屬性存在與否。Message.vue中有一個message屬性适篙,現(xiàn)在我們就可以斷言以下是否直接收到了傳遞進來的屬性值爪瓜。vue-test-utils有一個api是hasProp(prop, value)函數(shù),可以幫助我們方便進行檢測并斷言匙瘪。

it('has a message property', () => {
  cmp = createCmp({ message: 'hey' })
  expect(cmp.hasProp('message', 'hey')).toBeTruthy()
})

The properties behave in a way that they will be received only if they’re declared in the component. Meaning that if we pass a property that is not defined, it won’t be received. So to check for the no existence of a property, use a non-existing property:
一個屬性只有在組件中被聲明了铆铆,才能被接受到傳進的值,如果沒聲明丹喻,即使傳入薄货,那也接收不到。所以我們可以測試一下不存在是屬性是否收到了傳入的值:

it('has no cat property', () => {
  cmp = createCmp({ cat: 'hey' })
  expect(cmp.hasProp('cat', 'hey')).toBeFalsy()
})

However, in this case that test will fail because Vue has non-props attributes which sets it to the root of the Message component, thus being recognized as a prop and then the test will return true. Changing it to toBeTruty will make it pass for this example:
然而碍论,在這個用例中測試會失敗谅猾,因為Vue有non-props attributes特性,這個特性會將props中沒聲明的屬性設置在根組件中鳍悠,所以檢測如果將message斷言為props并檢測其值税娜,那么案例就會被通過。所以這個用例中可以修改如下藏研,將成功通過測試敬矩。

it('has no cat property', () => {
  cmp = createCmp({ cat: 'hey' });
  expect(cmp.hasProp('cat', 'hey')).toBeTruthy()
})

We can test the default value as well. Go to Message.vue and change the props as follows:
我們還可以測試props中屬性的默認值,代碼作如下修改:

props: {
  message: String,
  author: {
    type: String,
    default: 'Paco'
  }
},

Then the test could be:
然后測試用例可以如下面這樣寫:

it('Paco is the default author', () => {
  cmp = createCmp({ message: 'hey' })
  expect(cmp.hasProp('author', 'Paco')).toBeTruthy()
})

Asserting properties validation

對屬性的合法性進行檢測

Properties can have validation rules, ensuring that a property is required or it is of a determined type. Let’s write the message property as follows:
繼承屬性有一個合法性的規(guī)則蠢挡,能保證一個屬性是否是必備或符合既定規(guī)則』≡溃現(xiàn)在我們編輯message的屬性規(guī)則如下:

props: {
  message: {
    type: String,
    required: true,
    validator: message => message.length > 1
  }
}

Going further, you could use custom constructors types or custom validation rules, as you can see in the docs. Don’t do this right now, I’m just showing it as an example:
進一步的測試我們可以使用自定義構造函數(shù)的類型或自定義的驗證規(guī)則,設置屬性類型业踏。以下是例子:

class Message {}
...
props: {
  message: {
    type: Message, // It's compared using instance of
    ...
    }
  }
}

Whenever a validation rule is not fulfilled, Vue shows a console.error. For example, for createCmp({ message: 1 }), the next error will be shown:
無論何時禽炬,一個驗證規(guī)則如果不被滿足,Vue就會顯示一個打印錯誤勤家。下面是錯誤信息示例腹尖。

[Vue warn]: Invalid prop: type check failed for prop "message". Expected String, got Number.
(found in <Root>)

By the date of writing, vue-test-utils doesn’t have any utility to test this. We could use jest.spyOn to test it:
迄今為止,vue-test-utils還沒有相關工具可以測試這個錯誤伐脖,我們可以用jest.spyOn來搞定热幔。

it('message is of type string', () => {
  let spy = jest.spyOn(console, 'error')

  cmp = createCmp({ message: 1 })

  expect(spy).toBeCalledWith(expect.stringContaining('[Vue warn]: Invalid prop'))

  spy.mockReset() // or mockRestore() to completely remove the mock
  // 或者用 mockRestore() 來完全移除mock數(shù)據(jù)
})

Here we’re spying on the console.error function, and checking that it shows a message containing a specific string. This is not an ideal way to check it, since we’re spying on global objects and relying on side effects.
上述代碼暗中監(jiān)視了console.error函數(shù)乐设,檢測了顯示的信息中是否包含指定的字符串。這個不是一個理想的檢測方法断凶,因為我們用的這個方法監(jiān)視了一個全局對象伤提,并且對本想避免的副作用比較依賴巫俺。

Fortunately, there is an easier way to do it, which is by checking vm.options. Here’s where Vue stores the component options “expanded”. With expanded I mean: you can define your properties in a different ways: 幸運的是认烁,我們還有一個更簡單的方法 ——檢測`vm.options對象實現(xiàn)測試目的。vm.$options`對象存儲著組件的擴展選項介汹。擴展選項的意思是你可以用一種另類的方式定義你的繼承屬性却嗡。

props: ['message']

// or

props: {
  message: String
}

// or

props: {
  message: {
    type: String
  }
}

But they all will end up in the most expanded object form (like the last one). So if we check the cmp.vm.option.props.message, for the first case, they all will be in the { type: X } format (although for the first example it will be { type: null}) 以上是三種定義類型的表達方式,但是他們在大部分擴展對象中都會已最后一種格式表示嘹承。我們索引到`cmp.vm.option.props.message時窗价,會發(fā)現(xiàn)他們都會被格式化為{ type: X }的形式,不過第一種的結果是{ type: null}`叹卷。

With this in mind, we could write a test suite to test that asserts that the message property has the expected validation rules:
如此一來撼港,所以我們就可以寫個測試系列案例來斷言信息的屬性是否符合預期驗證規(guī)則。

describe('Message.test.js', () => {
//   ...
  describe('Properties', () => {
    // ...
    describe('Validation', () => {
      const message = createCmp().vm.$options.props.message

      it('message is of type string', () => {
        expect(message.type).toBe(String)
      })

      it('message is required', () => {
        expect(message.required).toBeTruthy()
      })

      it('message has at least length 2', () => {
        expect(message.validator && message.validator('a')).toBeFalsy()
        expect(message.validator && message.validator('aa')).toBeTruthy()
      })
    })
  })
})

Custom Events

自定義事件

We can test at least two things in Custom Events:
我們至少可以測試自定義事件的兩類情況:

  • Asserting that after an action an event gets triggered
    • 在一個action后斷言事件被觸發(fā)
  • Checking what an event listener calls when it gets triggered
    • 檢測事件被觸發(fā)時調用了什么方法

Which in the case of the MessageList.vue and Message.vue components example, that gets translated to:
對于MessageList.vueMessage.vue兩個組件骤竹,以上兩類相當于:

  • Assert that Message components triggers a message-clicked when a message gets clicked

    • 對Message組件斷言:如果一項message被點擊帝牡,會觸發(fā)message-clicked事件
  • Check in MessageList that when a message-clicked happens, a handleMessageClick function is called

    • 檢測MessageList組件中,當一項message被點擊蒙揣,handleMessageClick方法會被調用

First, go to Message.vue and use emit to trigger that custom event: 首先我們在Message組件中用emit來觸發(fā)一個自定義事件

<template>
    <li
      style="margin-top: 10px"
      class="message"
      @click="handleClick">
        {{message}}
    </li>
</template>

<script>
  export default {
    name: 'Message',
    props: ['message'],
    methods: {
      handleClick() {
        this.$emit('message-clicked', this.message)
      }
    }
  }
</script>

And in MessageList.vue, handle the event using @message-clicked:
在父組件MessageList.vue中靶溜,我們用@message-clicked綁定handleMessageClick方法,完成閉環(huán)

<template>
    <ul>
        <Message
          @message-clicked="handleMessageClick"
          :message="message"
          v-for="message in messages"
          :key="message"/>
    </ul>
</template>

<script>
import Message from './Message'

export default {
  name: 'MessageList',
  props: ['messages'],
  methods: {
    handleMessageClick(message) {
      console.log(message)
    }
  },
  components: {
    Message
  }
}
</script>

Now it’s time to write a unit test. Create a nested describe within the test/Message.spec.js file and prepare the barebones of the test case “Assert that Message components triggers a message-clicked when a message gets clicked” that we mentioned before:
現(xiàn)在已經(jīng)萬事俱備止潮,可以寫單元測試了点把。在test/Message.spec.js文件中創(chuàng)建一個嵌套describe語句拼缝,并為之前提到的Assert that Message components triggers a message-clicked when a message gets clicked這個測試用例準備準系統(tǒng):

// ...
describe('Message.test.js', () => {
  // ...
  describe('Events', () => {
    beforeEach(() => {
      cmp = createCmp({ message: 'Cat' })
    })

    it('calls handleClick when click on message', () => {
      // @TODO
    })
  })
})

Testing the Event Click calls a method handler

測試點擊事件調用的處理方法

The first thing we can test is that when clicking a message, the handleClick function gets called. For that we can use a trigger of the wrapper component, and a jest spy using spyOn function:
首先,我們先測試點擊單項信息后瓷炮,處理方法被調用。此處我們把jest的spyOn方法賦給message的容器組件來監(jiān)測觸發(fā)動作:

it('calls handleClick when click on message', () => {
  const spy = spyOn(cmp.vm, 'handleClick')
  cmp.update() // Forces to re-render, applying changes on template

  const el = cmp.find('.message').trigger('click')
  expect(cmp.vm.handleClick).toBeCalled()
})

See the cmp.update()? When we change things that are used in the template, handleClick in this case, and we want the template to apply the changes, we need to use the update function.
注意到cmp.update()方法了嗎递宅?當模板內容有改動時崭别,即處理方法成功被調用后,我們想讓模板內容按預期有所改變恐锣,我們需要用cmp.update()方法茅主。

Keep in mind that by using a spy the original method handleClick will be called. Probably you intentionally want that, but normally we want to avoid it and just check that on click the methods is indeed called. For that we can use a Jest Mock function:
要注意的是,我們預期只要檢測到處理方法被成功調用就ok了土榴,可是通常我們卻是要避免如此籠統(tǒng)的檢測诀姚,而是具體地檢查方法是否被直接調用。如此一來玷禽,我們需要用到Jest的Mock函數(shù):

it('calls handleClick when click on message', () => {
  cmp.vm.handleClick = jest.fn()
  cmp.update()

  const el = cmp.find('.message').trigger('click')
  expect(cmp.vm.handleClick).toBeCalled()
})

Here we’re totally replacing the handleClick method, accessible on the vm of the wrapper component returned by the mount function.
現(xiàn)在我們將處理方法替換掉赫段,改為檢測掛載方法返回的父組件實例上的vm對象呀打。

We can make it even easier by using setMethods helper that the official tools provide us:
甚至我們再簡單一些,直接用官方工具中的setMethods函數(shù):

it('calls handleClick when click on message', () => {
  const stub = jest.fn()
  cmp.setMethods({ handleClick: stub })

  const el = cmp.find('.message').trigger('click')
  expect(stub).toBeCalled()
})

Using setMethods is the suggested way to do it, since is an abstraction that official tools give us in case the Vue internals change.
這里推薦使用setMethods方法做這類測試糯笙,因為這是官方工具提供給我們的抽象方法贬丛,就是用來測試Vue組件內部數(shù)據(jù)變化的方法。

Testing the Custom Event message-clicked is emitted

測試message-clicked自定義事件被分發(fā)

We’ve tested that the click method calls it’s handler, but we haven’t tested that the handler emits the message-clicked event itself. We can call directly the handleClick method, and use a Jest Mock function in combination with the Vue vm on method: 我們已經(jīng)測試了點擊事件后處理方法被調用给涕,但仍然沒有測到`message-clicked`被處理方法分發(fā)后的狀況豺憔。我們可以直接調用處理方法,然后用jest的Mock方法結合Vue中vm的on方法來測試够庙。

it('triggers a message-clicked event when a handleClick method is called', () => {
  const stub = jest.fn()
  cmp.vm.$on('message-clicked', stub)
  cmp.vm.handleClick()

  expect(stub).toBeCalledWith('Cat')
})

See that here we’re using toBeCalledWith so we can assert exactly which parameters we expect, making the test even more robust. Not that we’re not using cmp.update() here, since we’re making no changes that need to propagate to the template.
可以看到我們運用toBeCalledWith方法恭应,精確斷言了我們期望的參數(shù),使得用例代碼十分簡潔清爽耘眨。之所以不用cmp.update()方法昼榛,是因為我們沒有做什么需要模板重新渲染的數(shù)據(jù)變更。

Testing the @message-clicked triggers an event

測試@message-clicked觸發(fā)的事件

For custom events, we cannot use the trigger method, since it’s just for DOM events. But, we can emit the event ourselves, by getting the Message component and using its vm.emit method. So add the following test to MessageList.test.js: 對于自定義事件剔难,我們不能用觸發(fā)事件的方法進行測試胆屿,因為那是基于DOM的測試方法。但是偶宫,我們可以通過Message組件的`vm.emit`方法分發(fā)事件非迹。代碼更改如下:

it('Calls handleMessageClick when @message-click happens', () => {
  const stub = jest.fn()
  cmp.setMethods({ handleMessageClick: stub })
  cmp.update()

  const el = cmp.find(Message).vm.$emit('message-clicked', 'cat')
  expect(stub).toBeCalledWith('cat')
})

I’ll leave up to you to test what handleMessageClicked does.
對handleMessageClicked的測試就不再一一贅述了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末读宙,一起剝皮案震驚了整個濱河市彻秆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌结闸,老刑警劉巖唇兑,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桦锄,居然都是意外死亡扎附,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門结耀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來留夜,“玉大人,你說我怎么就攤上這事图甜“啵” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵黑毅,是天一觀的道長嚼摩。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么枕面? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任愿卒,我火速辦了婚禮,結果婚禮上潮秘,老公的妹妹穿的比我還像新娘琼开。我一直安慰自己,他們只是感情好枕荞,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布柜候。 她就那樣靜靜地躺著,像睡著了一般买猖。 火紅的嫁衣襯著肌膚如雪改橘。 梳的紋絲不亂的頭發(fā)上滋尉,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天玉控,我揣著相機與錄音,去河邊找鬼狮惜。 笑死高诺,一個胖子當著我的面吹牛,可吹牛的內容都是我干的碾篡。 我是一名探鬼主播虱而,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼开泽!你這毒婦竟也來了牡拇?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤穆律,失蹤者是張志新(化名)和其女友劉穎惠呼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峦耘,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡剔蹋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辅髓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泣崩。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洛口,靈堂內的尸體忽然破棺而出矫付,到底是詐尸還是另有隱情,我是刑警寧澤第焰,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布买优,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏而叼。R本人自食惡果不足惜身笤,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望葵陵。 院中可真熱鬧液荸,春花似錦、人聲如沸脱篙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绊困。三九已至文搂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秤朗,已是汗流浹背煤蹭。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留取视,地道東北人硝皂。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像作谭,于是被迫代替她去往敵國和親稽物。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容