iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
1
Modern Web

循序漸進學習 Javascript 測試系列 第 26

Day 26 測試 React 元件:使用 React Testing Library 體驗 Test Driven Development (TDD) - 6

前面幾天我們已經用 TDD 的方式完成了 <Editor /> 元件,但不要忘了 TDD 中很重要的「重構」部分。在 __tests__/post-editor.js 的兩個測試中有重複的邏輯,這些重複的邏輯對於未來的維護來說會增加不必要的負擔,不易分辨每個測試之間的差別是什麼。現在我們要來重構這些重複的程式碼,讓每個測試之間的關注點更明確。

下面是 __tests__/post-editor.js 目前的兩個測試:

tests/post-editor.js

test('renders a form with title, content, tags, and a submit button', async () => {
  mockSavePost.mockResolvedValueOnce()
  const fakeUser = userBuilder()
  const {getByLabelText, getByText} = render(<Editor user={fakeUser} />)
  const fakePost = postBuilder()

  getByLabelText(/title/i).value = fakePost.title
  getByLabelText(/content/i).value = fakePost.content
  getByLabelText(/tags/i).value = fakePost.tags.join(', ')
  const submitButton = getByText(/submit/i)

  fireEvent.click(submitButton)

  expect(submitButton).toBeDisabled()

  expect(mockSavePost).toHaveBeenCalledWith({
    ...fakePost,
    authorId: fakeUser.id,
  })
  expect(mockSavePost).toHaveBeenCalledTimes(1)

  await wait(() => expect(MockRedirect).toHaveBeenCalledWith({to: '/'}, {}))
})

test('renders an error message from the server', async () => {
  const testError = 'test error'
  mockSavePost.mockRejectedValueOnce({data: {error: testError}})
  const fakeUser = userBuilder()
  const {getByText, findByRole} = render(<Editor user={fakeUser} />)
  const submitButton = getByText(/submit/i)

  fireEvent.click(submitButton)

  const postError = await findByRole('alert')
  expect(postError).toHaveTextContent(testError)
  expect(submitButton).not.toBeDisabled()
})

可以發現下面這幾行是兩個測試中重複出現的邏輯:

const fakeUser = userBuilder()
const {getByLabelText, getByText} = render(<Editor user={fakeUser} />)
const fakePost = postBuilder()

getByLabelText(/title/i).value = fakePost.title
getByLabelText(/content/i).value = fakePost.content
getByLabelText(/tags/i).value = fakePost.tags.join(', ')
const submitButton = getByText(/submit/i)

新增一個 function renderEditor ,將重複的程式碼抽到這個 funtion。將 render() method 回傳的結果宣告為 utils 變數。renderEditor 回傳一個 object,裡面有展開後的 utils 屬性( ...utils )、 submitButtonfakeUserfakePost

tests/post-editor.js

function renderEditor() {
  const fakeUser = userBuilder()
  const utils = render(<Editor user={fakeUser} />)
  const fakePost = postBuilder()

  utils.getByLabelText(/title/i).value = fakePost.title
  utils.getByLabelText(/content/i).value = fakePost.content
  utils.getByLabelText(/tags/i).value = fakePost.tags.join(', ')
  const submitButton = utils.getByText(/submit/i)
  return {
    ...utils,
    submitButton,
    fakeUser,
    fakePost,
  }
}

第一個測試原本被抽出的程式碼,現在可以取代為 const {submitButton, fakePost, fakeUser} = renderEditor()

test('renders a form with title, content, tags, and a submit button', async () => {
  mockSavePost.mockResolvedValueOnce()
  const {submitButton, fakePost, fakeUser} = renderEditor()

  fireEvent.click(submitButton)

  expect(submitButton).toBeDisabled()

  expect(mockSavePost).toHaveBeenCalledWith({
    ...fakePost,
    authorId: fakeUser.id,
  })

  ...
}

第二個測試原本被抽出的程式碼,現在可以取代為 const {submitButton, findByRole} = renderEditor()

test('renders an error message from the server', async () => {
  const testError = 'test error'
  mockSavePost.mockRejectedValueOnce({data: {error: testError}})
  const {submitButton, findByRole} = renderEditor()

  fireEvent.click(submitButton)

  const postError = await findByRole('alert')
  expect(postError).toHaveTextContent(testError)
  expect(submitButton).not.toBeDisabled()
})

重構後,測試亮綠燈 ✅,代表沒問題。


上一篇
Day 25 測試 React 元件:使用 React Testing Library 體驗 Test Driven Development (TDD) - 5
下一篇
Day 27 測試 React 元件:測試 react-router
系列文
循序漸進學習 Javascript 測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言