現階段我們已經具備了新增跟移除商品的機制,然而要跟後端搭配的話就無法避免跟真實的 API 來進行串接,在測試環境中就不會那麼好處理。我們可以利用 Playwright 的 Mock API 機制來模擬我們想要的 API 回應。
商品資料
如果要驗證實作出來的前端正確無誤,我們會需要提供不同的測試資料來進行驗證。然而,我們是透過啟動一個 Vite 伺服器來進行處理,因此無法直接在 Cucumber 的實作中修改 Vue 放在記憶體中的變數。
那麼,為了實現這件事情,就需要讓我們可以透過呼叫 API 的方式來載入這些資料。首先,先對原本的 Cucumber 測試做出調整,加入描述測試資料的部分。
1#language:zh-TW
2功能: 商品列表
3 背景:
4 假設 這裡有一些商品
5 | id | name | price |
6 | 1 | Ruby 秘笈 | 100 |
7 | 2 | RSpec 秘笈 | 150 |
8 當 開啟網站
9
10# ...
在前面的例子中,我們都是直接寫死在 src/App.vue
中來通過測試。因為 Cucumber 是以一個功能(Feature)為單位來描述,我們應該嘗試將完整的狀況在裡面描述,那麼商品資料也最好能像這樣反應出來。
接下來,我們要加入「這裡有一些商品」這個步驟應有的反應。
1Given('這裡有一些商品', async function(this: World, data: DataTable) {
2 await this.page.route('/api/products', async route => {
3 const json = data.hashes().map(item => ({
4 id: Number(item.id),
5 price: Number(item.price),
6 name: item.name
7 }))
8
9 await route.fulfill({ json })
10 })
11})
在這段程式碼中,利用 Playwright 的 Mock API 機制,讓我們呼叫 /api/products
這個路徑時,會直接回應我們設定的內容,而不是實際上進行這個呼叫。
因為 Cucumber 的 DataTable
物件剛好可以回傳一個我們預期的資料結構,可以直接用 { json: data.hashes()
作為傳回值,讓我們在 Vue 端可以拿到像這樣的內容。
1[
2 { "id": 1, "name": "Ruby 秘笈", "price": 100 },
3 { "id": 2, "name": "RSpec 秘笈", "price": 150 },
4]
總價計算
現在我們得到了一個帶有更多資訊的商品資料,接下來要在原本計算商品數量的前提下,再繼續擴充出「總價計算」的情境,因此還需要一個對應的測試案例來反應這件事情。
1#language:zh-TW
2# ...
3 場景: 蒼時可以對商品點選加入購物車,並看到總金額發生變化
4 當 把 "<name>" 加入購物車
5 那麼 可以看見購物車總金額為 "<subtotal>"
6 例子:
7 | name | subtotal |
8 | Ruby 秘笈 | $100 |
9 | RSpec 秘笈 | $150 |
為了驗證金額會確實的產生變化,這次我們透過舉例的方式,來反應不同商品會得到不一樣的金額。接下來要增加新的步驟實作,並且稍微調整原本的 cart
為 cart-amount
來對應不同購物車屬性的元素。
1Then('可以看見購物車商品數為 {int}', async function(this: World, amount: number) {
2 await expect(this.page.getByTestId('cart-amount')).toHaveText(`一共 ${amount} 項商品`)
3})
4
5Then('可以看見購物車總金額為 {string}', async function(this: World, subtotal: string) {
6 await expect(this.page.getByTestId('cart-subtotal')).toHaveText(`總金額為 ${subtotal}`)
7})
有了新的定義後,再繼續修改 src/App.vue
調整我們的 HTML 結構來將購物車組合成一個群組,顯示不同的資訊。
1<template>
2 <div data-testid="cart">
3 <div data-testid="cart-amount">一共 {{ totalItems }} 項商品</div>
4 <div data-testid="cart-subtotal">總金額為 ${{ subtotal }}</div>
5 </div>
6 <!-- 略 -->
7</template>
因為增加了 {{ subtotal }}
的呼叫,我們還需要調整 TypeScript 的實作,同時要將透過 API 載入商品資料的部分實作進來。
1<script setup lang="ts">
2import { ref, reactive, computed } from 'vue'
3
4const products = ref([])
5const findByProductId = id => products.value.find(product => product.id === Number(id))
6
7fetch('/api/products')
8 .then(res => res.json())
9 .then(data => products.value = data)
10
11// ...
12
13const subtotal = computed(() => Object.entries(cart).reduce((prev, [id, count]) => prev + findByProductId(id)?.price * count, 0))
14</script>
首先,我們讓 products
變數成為一個 Vue 的參考,並且實作了 findByProductId()
方法來作為後續 subtotal
計算的輔助,並且用 fetch
來載入商品資料,最後用跟 totalItems
相同方式,以 computed()
的實作來製作一個可以反映變化的實作。
現階段直接用瀏覽器預覽會發生錯誤,我們還沒有真實的後端 API 可用,然而運行 yarn run cucumber-js
是可以順利通過測試,因為我們利用 Playwright 來幫助我們模擬後端。
這篇文章的例子是作為示範用只有單一檔案,因此直接呼叫
fetch()
來取的商品資料,在正式專案或者比較完整的實作中,還是推薦封裝成 API Client 來使用。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 同時完成測試與文件 - Cucumber 的文件測試法
- 基本語法:功能描述 - Cucumber 的文件測試法
- 基本語法:驗證行為 - Cucumber 的文件測試法
- 基本語法:步驟定義 - Cucumber 的文件測試法
- 基本語法:輔助設定 - Cucumber 的文件測試法
- 前端環境:Vite 與 Cucumber - Cucumber 的文件測試法
- 商品列表與加入購物車 - Cucumber 的文件測試法
- 重構與移出購物車 - Cucumber 的文件測試法
- 商品資料與總價 - Cucumber 的文件測試法
- 結帳與結果 - Cucumber 的文件測試法
- 整理前端實作 - Cucumber 的文件測試法
- 初始化後端專案 - Cucumber 的文件測試法
- 商品資料 API - Cucumber 的文件測試法
- 更新購物車 API - Cucumber 的文件測試法
- 加入資料模型 - Cucumber 的文件測試法
- 持久化保存 - Cucumber 的文件測試法
- 結帳處理 - Cucumber 的文件測試法
- 在 Rails 的前後端分離 - Cucumber 的文件測試法
- 匯入前端實作 - Cucumber 的文件測試法
- 重現後端實作 - Cucumber 的文件測試法
- 累積價值 - Cucumber 的文件測試法