Phoenix 菜譜視圖

2023-12-18 15:01 更新

我們執(zhí)行 mix phx.gen.html 命令時,它會生成如下文件:

  • a schema in web/models
  • a view in web/views
  • a controller in web/controllers
  • a migration file for the repository
  • default CRUD templates in web/templates
  • test files for generated model and controller

其中有兩個測試文件,但是沒有視圖的測試文件 - 為什么?難道視圖不重要?不不不,只要是代碼,都會有測試的必要 - 有時沒寫,只是一個優(yōu)先級或是投入產(chǎn)出比上的考慮。那如何入手?

查看 test/views 目錄,現(xiàn)在已經(jīng)有三個文件。我們可以借鑒其中的 error_view_test.exs 文件。

首先在 test/views 目錄下新建一個 recipe_view_test.exs 文件,然后準備好如下內(nèi)容:

defmodule TvRecipeWeb.RecipeViewTest do
  use TvRecipeWeb.ConnCase, async: true

  # Bring render/3 and render_to_string/3 for testing custom views
  import Phoenix.View

end

前面章節(jié)里我們曾經(jīng)提到過,templates 文件會被編譯成 view 模塊下的函數(shù)。比如我們的 web/templates/recipe/index.html.eex 文件,最終會變成 TvRecipe.RecipeView 模塊中的一個函數(shù):

def render("index.html", assigns) do
  # 返回編譯后的 eex 模板
end

換句話說,我們其實可以在 TvRecipe.RecipeView 中定義 render("index.html", assigns),而不必再寫一個 index.html.eex 模板。只是模板對我們的開發(fā)更為友好,所以才從 View 中分離出來。

因此,測試模板即測試 View 中的函數(shù)。

那么,測試什么?測試我們的期望與事實是否相符。

recipe/index.html.eex 模板說,我們想在模板頁面上顯示哪些數(shù)據(jù)?Phoenix 最后的輸出是否確保顯示了?

來加一個測試:

diff --git a/test/views/recipe_view_test.exs b/test/views/recipe_view_test.exs
index be4148a..8174c14 100644
--- a/test/views/recipe_view_test.exs
+++ b/test/views/recipe_view_test.exs
@@ -4,4 +4,28 @@ defmodule TvRecipe.RecipeViewTest do
   # Bring render/3 and render_to_string/3 for testing custom views
   import Phoenix.View

+  alias TvRecipe.Recipes.Recipe
+  @recipe1 %{id: "1", name: "淘米", title: "俠飯", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999"}
+  @recipe2 %{id: "2", name: "煮飯", title: "俠飯", season: "1", episode: "1", content: "浸泡", user_id: "888"}
+
+  test "render index.html", %{conn: conn} do
+    recipes = [struct(Recipe, @recipe1), struct(Recipe, @recipe2)]
+    content = render_to_string(TvRecipeWeb.RecipeView, "index.html", conn: conn, recipes: recipes)
+    # 頁面上包含標題 Listing recipes
+    assert String.contains?(content, "Listing Recipes")
+    for recipe <- recipes do
+      # 頁面上包含菜譜名
+      assert String.contains?(content, recipe.name)
+      # 頁面上包含節(jié)目名
+      assert String.contains?(content, recipe.title)
+      # 包含 season
+      assert String.contains?(content, recipe.season)
+      # 包含 episode
+      assert String.contains?(content, recipe.episode)
+      # 不包含所有者 id
+      refute String.contains?(content, recipe.user_id)
+      # 因為 content 很長,我們不在 index.html 里顯示
+      refute String.contains?(content, recipe.content)
+    end
+  end
+
 end

你可能要問,測試里的 season 為什么是 "1" 而不是 1,要知道我們給它定義的類型是 integer

是的,我們本應該把它寫為 1,但那樣的話,我們的測試代碼里就要做類型轉換:

assert String.contains?(content, Integer.to_string(recipe.season))

為了省事,我們就直接把 1 寫成了 "1" - 這并不會有問題,因為 Phoenix 在編譯模板時,也會做類型轉換

iex> EEx.eval_string "foo <%= bar %>", [bar: 123]
"foo 123"

運行測試:

$ mix test
Compiling 1 file (.ex)
...

  1) test render index.html (TvRecipe.RecipeViewTest)
     test/views/recipe_view_test.exs:9
     Expected false or nil, got true
     code: String.contains?(content, recipe.user_id())
     stacktrace:
       test/views/recipe_view_test.exs:25: anonymous fn/3 in TvRecipe.RecipeViewTest.test render index.html/1
       (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
       test/views/recipe_view_test.exs:15: (test)

....................................................

Finished in 0.8 seconds
58 tests, 1 failure

一個錯誤發(fā)生,因為目前的 index.html.eex 中包含了 contentuser_id 的內(nèi)容。

我們調整下 index.html.eex 文件:

diff --git a/web/templates/recipe/index.html.eex b/web/templates/recipe/index.html.eex
index b6ff40b..1dc8a3a 100644
--- a/web/templates/recipe/index.html.eex
+++ b/web/templates/recipe/index.html.eex
@@ -7,8 +7,6 @@
       <th>Title</th>
       <th>Season</th>
       <th>Episode</th>
-      <th>Content</th>
-      <th>User</th>

       <th></th>
     </tr>
@@ -20,8 +18,6 @@
       <td><%= recipe.title %></td>
       <td><%= recipe.season %></td>
       <td><%= recipe.episode %></td>
-      <td><%= recipe.content %></td>
-      <td><%= recipe.user_id %></td>

       <td class="text-right">
         <%= link "Show", to: recipe_path(@conn, :show, recipe), class: "btn btn-default btn-xs" %>

再運行測試:

mix test
........................................................

Finished in 0.8 seconds
58 tests, 0 failures

悉數(shù)通過。

我們知道,index.html.eex 頁面上有一個 New recipe 的按鈕,那我們的測試里是否需要體現(xiàn)?Show、EditDelete 這些按鈕呢?要不要給它們寫測試?

測試測什么,測到怎么的粒度,我覺得沒有標準答案,更多時候要根據(jù)項目情況去權衡。但如果一定要有一個什么做為參考,我會選擇設計稿。設計稿上定下來的元素,我們就盡量在測試里體現(xiàn) - 否則設計人員很容易找上門來,說怎么少了這少了那。

在結束本節(jié)之前,別忘了在菜單欄上加上“菜譜”,不然我們就只能通過修改 url 訪問菜譜相關頁面了:

diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_controller_test.exs
index a1b75c6..7bd839c 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
@@ -29,6 +29,7 @@ defmodule TvRecipe.UserControllerTest do
     # 注冊后自動登錄,檢查首頁是否包含用戶名
     conn = get conn, Routes.page_path(conn, :index)
     assert html_response(conn, 200) =~ Map.get(@valid_attrs, :username)
+    assert html_response(conn, 200) =~ "菜譜"
   end

   test "does not create resource and renders errors when data is invalid", %{conn: conn} do
diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex
index b13f370..49240c9 100644
--- a/web/templates/layout/app.html.eex
+++ b/web/templates/layout/app.html.eex
@@ -19,6 +19,7 @@
             <li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
             <%= if @current_user do %>
               <li><%= link @current_user.username, to: Routes.user_path(@conn, :show, @current_user) %></li>
+              <li><%= link "菜譜", to: Routes.recipe_path(@conn, :index) %></li>
               <li><%= link "退出", to: Routes.session_path(@conn, :delete, @current_user), method: "delete" %></li>
             <% else %>
               <li><%= link "登錄", to: Routes.session_path(@conn, :new) %></li>

運行測試:

$ mix test
..........................................................

Finished in 0.8 seconds
58 tests, 0 failures


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號