iT邦幫忙

2021 iThome 鐵人賽

DAY 18
1
Modern Web

前端工程師在工作中的各種實戰技能 (Vue 3)系列 第 18

[Day18] Vue 3 單元測試 (Unit Testing) - Conditional rendering & Elements visibility

  • 分享至 

  • xImage
  •  

Conditional Rendering

在寫元件時最常見的就是會使用 v-if 來動態插入和刪除元素,我們馬上來看看下面的範例程式。

const Component = {
  template: `
    <nav>
      <a id="profile" href="/profile">My Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data () {
    return {
      admin: false
    }
  }
}

在元件中有兩個連結,一個為指向 /profile 的連結,另一個為指向 /admin 的連結,不過 /admin 連結只有在 admin 值為 true 時才會顯示。

要測試這個元件是否正常,我們應該驗證三種情況是否正確運行:

  • Case 1: 應該顯示 /profile 連結。
  • Case 2: 當 admin 值為 false 時,不應該顯示 /admin 連結。
  • Case 3: 當 admin 值為 true 時,應該顯示 /admin 連結。

Case 1: 應該顯示 /profile 連結

test('always render profile link', () => {
  const wrapper = mount(Component)
  const profileLink = wrapper.get('#profile')
  expect(profileLink.text()).toBe('My Profile')
})

語法說明:

  • get() : VueWrapper 有一個 get 方法來搜索現有元素,它使用和 Document.querySelector() 一樣的語法,如果 get() 沒有找到目標元素,它會拋出錯誤並導致測試失敗。如果找到的話則會回傳一個 DOMWrapper。

    DOMWrapper 是圍繞 Wrapper API 的 DOM 元素的瘦包裝器 (thin wrapper)。

  • text() : 回傳元素的文本內容 (text content)。

Case 2: 當 admin 值為 false 時,不應該顯示 /admin 連結。

test('does not render an admin link', () => {
  const wrapper = mount(Component)

  expect(wrapper.find('#admin').exists()).toBe(false)
})

語法說明:

  • find(): find() 和 get() 很像,一樣是使用 Document.querySelector() 的語法,不過差別在於 find() 沒有找到目標元素時不會拋出錯誤。

    除非斷言的內容可能不存在,否則盡量都使用 get 而不是 find ,因為如果不存在時就表示真的有錯誤。

  • exist(): 檢查元素是否存在。

Case 3: 當 admin 值為 true 時,應該顯示 /admin 連結。

test('only render admin link when admin is true', () => {
  const wrapper = mount(Component, {
    data () {
      return {
        admin: true
      }
    }
  })

  expect(wrapper.get('#admin').text()).toBe('Admin')
})

語法說明:

  • mount() 的第二個參數是可以用來定義元件的狀態 (state) 配置,例如 props, data, attrs 等等,因此這次我們就傳入 data 覆蓋掉元件中的預設值。

Checking Elements visibility

有時候我們只想隱藏一個元素,但不想將它從 DOM 中移除,此時我們可以使用 v-show 來實現這件事。

const Component = {
  template: `
    <nav>
      <a id="user" href="/profile">My Profile</a>
      <ul v-show="expandDropdown" id="user-dropdown">
        <!-- dropdown content -->
      </ul>
    </nav>
  `,
  data () {
    return {
      expandDropdown: false
    }
  }
}

不過和上面的例子不同的是,元素雖然不會渲染出來,但它其實是存在於 DOM tree 中的,所以如果使用 get() 或 find() 都會確實找到目標元素,但這其實不是我們想要的結果。

因此面對這樣的情況,我們可以使用 isVisible() 來解決這個問題,isVisible 是專門用來檢查元素是否為隱藏的狀態,例如:

  • 元素或元素的祖先中有 display: none、visibility: hidden、opacity:0 的樣式。
  • 元素或元素的祖先在收合的 <details> 標籤中。
  • 元素或元素的祖先具有 hidden 屬性。
test('does not show the user dropdown', () => {
  const wrapper = mount(Component)

  expect(wrapper.get('#user-dropdown').isVisible()).toBe(false)
})

善用 dataset

在寫測試時,我們總是會透過 get() 或 find() 來尋找目標的元素,而通常我們要尋找元素時使用的選擇器 (selectors) 可能習慣會是透過 id 或者是 class,然而我想建議大家使用 dataset 來當作選擇器,如下:

import { mount } from '@vue/test-utils'

const Component = {
  template: '<div data-test="target">dataset</div>'
}

test('render dataset', () => {
  const wrapper = mount(Component)

  expect(wrapper.get('[data-test="target"]').text()).toBe('dataset')
})

原因是 id 或 class 通常都是因為其他用途而存在的,所以我們可能會在修改程式碼的過程中而不小心去修改 id 或是 class 造成測試不通過,那如果是用 dataset 的話, 就不會有這個問題了,甚至在開發時也很清楚知道這段程式碼和測試有關而特別小心修改。

參考資料


今天的分享就到這邊,如果大家對我分享的內容有興趣歡迎點擊追蹤 & 訂閱系列文章,如果對內容有任何疑問,或是文章內容有錯誤,都非常歡迎留言討論或指教的!

明天要來分享的是 Vue3 單元測試 (Unit Testing) 主題的第四篇 Event Handling ,那我們明天見!


上一篇
[Day17] Vue 3 單元測試 (Unit Testing) - Vue Test Utils + Jest 基本範例 & 核心語法
下一篇
[Day19] Vue 3 單元測試 (Unit Testing) - Event Handling
系列文
前端工程師在工作中的各種實戰技能 (Vue 3)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
TD
iT邦新手 4 級 ‧ 2021-10-09 15:53:10

在善用 dataset 這裡的意思是,我們要刻意提供一個 data-test 給 component 嗎?

Mia Yang iT邦新手 5 級 ‧ 2021-10-09 16:52:14 檢舉

TD 沒錯,我們要刻意提供一個 data-test 給 HTML tag,原因在內容也有提及,因為通常 id 和 class 都是因為其他原因而添加上去的,不會想到和測試有關,也因此在日後維護時容易更動到導致測試不通過。

我要留言

立即登入留言