---
title: "在 Rails 的前後端分離 - Cucumber 的文件測試法"
date: 2024-05-03T00:00:00+08:00
publishDate: 2024-05-03T00:00:00+08:00
lastmod: 2023-12-06T15:50:15+08:00
tags: ["Cucumber","教學","測試","後端","Rails","ViteRuby","Vite"]
series: "test-with-cucumber"
toc: true
permalink: "https://blog.aotoki.me/posts/2024/05/03/test-with-cucumber-frontend-in-rails/"
language: "zh-tw"
---


在前面的實作中我們將前端跟後端分別獨立進行開發，這是很常見的一種方式。然而在 Rails 7 的生態系裡面，透過 [jsbundling-rails](https://github.com/rails/jsbundling-rails) 可以很輕鬆的整合在一起，接下來我們會使用類似基礎的 [ViteRuby](https://vite-ruby.netlify.app/) 把前端的 Cucumber 測試搬移到 Rails 上重現一次性整合完畢的實作。

<!--more-->

## 安裝 Vite Ruby{#install-vite-ruby}

要安裝 ViteRuby 非常簡單，開發團隊已經準備好了 [vite_rails](https://github.com/ElMassimo/vite_ruby/tree/main/vite_rails) 可以直接套用到 Rails 專案中，關於 Rails 中設定 Cucumber 等開發環境的部分，可以直接使用我預先製作好的樣板 [rails-developer-foundation-template](https://github.com/StarPortal/rails-developer-foundation-template) 來進行開發。

首先，我們基於樣板建立新的專案，然後運行以下命令加入 ViteRails 套件。

```bash
bundle add vite_rails
bundle exec vite install
```

執行完畢後會自動將原有的 `jsbundling-rails` 做一些更新，我們只需要驗證 `bin/vite dev` 是否可以正確運行即可。

## 安裝 Vue{#install-vue}

Vite 的定位是簡化前端開發的工具，因此預設不會有 Vue 的環境存在，我們需要在 ViteRuby 生成的基礎下手動將 Vue 加入到專案中。

> 所需的設定可以參考前面純前端版本的實作複製設定，我們在這邊只會做必要的調整。

運行以下命令把 Vue 和 Vite 的外掛安裝進去。

```bash
yarn add vue vite-plugin-ruby
```

接下來修改自動生成的 `vite.config.ts` 加入 Vue 的外掛，大致上會呈現這個樣子。

```ts
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import Vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    RubyPlugin(),
    Vue(),
  ],
})
```

這樣一來我們就可以在專案中使用 Vue 來進行開發，然而目前會把 `jsbundling-rails` 和 ViteRuby 的設定混在一起，因此我們還需要清理一下 Rails 的 Layout。

修改 `app/views/layout/application.html.erb` 把 jsbundling 相關的部分清除掉，留下 ViteRuby 的引用，並且加入 Vue 要附加的 Div 元素。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>DeveloperFoundation</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= vite_client_tag %>
    <%= vite_typescript_tag 'application', "data-turbo-track": "reload", defer: true %>
  </head>

  <body>
    <div id="app">
      <%= yield %>
    </div>
  </body>
</html>
```

這裡只加入最低限度的 JavaScript 實際專案中使用可以根據情況調整，並且我們預設所有頁面都會被 Vue 替換掉，因此將 `<%= yield %>` 用 `<div>` 標籤包覆起來。

> 因為預設採用 TypeScript 的關係，別忘記將 `app/javascript/entrypoints/application.js` 的副檔名替換為 `.ts`

## 用 Cucumber 驗證環境{#verify-environment-with-cucumber}

最後，我們要驗證我們可以用 Cucumber 來對前端做測試，加入 `features/products.feature` 和以下內容，先用於確認 Vue 正確的被載入。

```gherkin
#language:zh-TW
@javascript
功能: 商品列表
  場景: 當開啟網站時，可以看到 Hello, ViteRuby
    當 開啟網站
    那麼 可以看見 "Hello, ViteRuby"
```

這裡會發現我們額外加入了 `@javascript` 的標籤，這會讓 Cucumber 意識到需要呼叫 Capybara 使用真實的瀏覽器運行去模擬，關於對應的設定可以參考樣板中 `features/support/env.rb` 的內容，以及 [cucumber-rails](https://github.com/cucumber/cucumber-rails) 的文件。

接著加入 `feature/step_definitions/common.rb` 來定義步驟，雖然我們可以沿用 `.feature` 文件，然而在不同語言跟環境中，會需要用對應的語言重新實作步驟定義。

```ruby
# frozen_string_literal: true

When('開啟網站') do
  visit root_path
end

Then('可以看見 {string}') do |text|
  expect(page).to have_text(text)
end
```

這邊使用 Capybara 的匹配器（Matcher）來檢查畫面上有出現的文字。

接下來我們修改 `app/javascript/entrypoints/application.ts` 的內容從預設的樣板，改為初始化 Vue 的實作。

```ts
import { createApp } from 'vue'
import App from '@/App.vue'

const app = createApp(App)
app.mount('#app')
```

再將 `app/javascript/App.vue` 補到專案裡面，寫上 `Hello, ViteRuby` 來配合我們的第一個測試。

```vue
<template>
  <div>Hello, ViteRuby</div>
</template>
```

最後補上一個空的 Controller 和 View 讓我們可以將畫面渲染出來。

```ruby
Rails.application.routes.draw do
  root 'stores#index'
end
```

```ruby
# app/controllers/stores_controller.rb

class StoresController < ApplicationController
end
```

```bash
mkdir -p app/views/stores
touch app/views/stores/index.html.erb
```

接下來運行 `bundle exec cucumber` 確認是否能正常通過測試，如果遇到 JavaScript 檔案不存在的話，可以運行 `./bin/dev` 或者 `bundle exec rake assets:precompile` 來生成，在 Rails 專案樣板中預設使用 Chrome Headless 模式，如果是自己安裝 Cucumber 的話要可以看到跳出瀏覽器開啟畫面的樣子。

接下來，就要把前端的實作轉移到 Rails 上來重現相同的功能。

