Testing LiveView

Testing LiveView

Curious how to test LiveView?

Look no further! We’ll learn to write effective LiveView tests using our application's domain language. We'll use the exceptional tools built into LiveViewTest. And we'll see how our LiveView tests can to teach us about the design of our LiveViews. So join me, and let's take a deep dive into LiveView testing.

Key takeaway:

Write tests from the perspective of the user (testing behavior, not implementation) in the domain of your application.

Links mentioned in the talk:

- https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html
- https://www.tddphoenix.com/
- https://twitter.com/germsvel

A19876f5694283f26a061746ba82c3b0?s=128

German Velasco

September 04, 2020
Tweet

Transcript

  1. Testing LiveView

  2. German Velasco

  3. None
  4. Phoenix.LiveViewTest josevalim - chrismccord - preciz alexgaribay - SherSpock -

    wojtekmach nthock - NikitaAvvakumov - sneako mcrumm - leandrocp - jmbejar henrik - JonRowe - feliperenan orthodoX - davydog187 - qrede cadebward - axelclark - SolbiatiAlessandro adamvaughan - glennr - sevenseacat - egze
  5. How to write effective tests

  6. Learn by doing

  7. Learn by doing → How to write LiveView tests

  8. Learn by doing → How to write LiveView tests →

    Testing the right things
  9. Learn by doing → How to write LiveView tests →

    Testing the right things → Writing explicit tests
  10. None
  11. Version 0.14.4

  12. None
  13. How to write LiveView tests

  14. Setup defmodule MyAppWeb.TodoLiveTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest end

  15. Setup defmodule MyAppWeb.TodoLiveTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest end

  16. Setup defmodule MyAppWeb.TodoLiveTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest end

  17. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  18. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  19. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  20. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  21. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  22. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  23. Anatomy of a test {:ok, view, _html} = live(conn, "/todo")

    rendered = view |> element("li", "Hold council of Elrond") |> render_click() assert rendered =~ "Tell tale of Elendil" assert has_element?(view, "p", "Tell tale of Elendil")
  24. live/2 {:ok, view, html} = live(conn, "/todo") assert html =~

    "Your Todo" assert render(view) =~ "Your Todo"
  25. live/2 {:ok, view, html} = live(conn, "/todo") assert html =~

    "Your Todo" assert render(view) =~ "Your Todo"
  26. live/2 {:ok, view, html} = live(conn, "/todo") assert html =~

    "Your Todo" assert render(view) =~ "Your Todo"
  27. live/2 {:ok, view, html} = live(conn, "/todo") assert html =~

    "Your Todo" assert render(view) =~ "Your Todo"
  28. element/3 view |> element("li", "Hold council of Elrond") %Element{ selector:

    "li", text_filter: "Hold council of Elrond" }
  29. element/3 view |> element("li", "Hold council of Elrond") %Element{ selector:

    "li", text_filter: "Hold council of Elrond" }
  30. form/3 view |> form("#attendee-form", %{ email: "gandalf@thefellowship.com" }) %Element{ selector:

    "#attendee-form", form_data: %{ email: "gandalf@thefellowship.com" } }
  31. form/3 view |> form("#attendee-form", %{ email: "gandalf@thefellowship.com" }) %Element{ selector:

    "#attendee-form", form_data: %{ email: "gandalf@thefellowship.com" } }
  32. CSS selectors selector example classes ".button" ids "#user-name" tags "h1"

    data attributes "[data-role=user-heading]"
  33. render_* functions render_blur/2 render_blur/3 render_change/2 render_change/3 render_click/2 render_click/3 render_focus/2 render_focus/3

    render_keydown/2 render_keydown/3 render_keyup/2 render_keyup/3 render_submit/2 render_submit/3
  34. Assertions assert rendered =~ "Tell tale of Elendil" assert has_element?(view,

    "p", "Tell tale of Elendil")
  35. Assertions assert rendered =~ "Tell tale of Elendil" assert has_element?(view,

    "p", "Tell tale of Elendil")
  36. Example: Adding a new todo

  37. Adding a new todo

  38. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  39. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  40. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  41. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  42. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  43. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  44. Two alternatives:

  45. Two alternatives: → Use render_submit/3 without form/3.

  46. Two alternatives: → Use render_submit/3 without form/3. → Assert rendered

    text
  47. render_submit/3 test "user can add a new todo", %{conn: conn}

    do {:ok, view, _html} = live(conn, "/todo") view - |> form("#new-todo", todo: %{text: "Decide fate of ring"}) - |> render_submit() + |> render_submit("create-todo", todo: %{text: "Decide fate of ring"}) assert has_element?(view, ".todo", "Decide fate of ring") end
  48. render_submit/3 test "user can add a new todo", %{conn: conn}

    do {:ok, view, _html} = live(conn, "/todo") view - |> form("#new-todo", todo: %{text: "Decide fate of ring"}) - |> render_submit() + |> render_submit("create-todo", todo: %{text: "Decide fate of ring"}) assert has_element?(view, ".todo", "Decide fate of ring") end
  49. render_submit/3 test "user can add a new todo", %{conn: conn}

    do {:ok, view, _html} = live(conn, "/todo") view - |> form("#new-todo", todo: %{text: "Decide fate of ring"}) - |> render_submit() + |> render_submit("create-todo", todo: %{text: "Decide fate of ring"}) assert has_element?(view, ".todo", "Decide fate of ring") end
  50. Targets this directly def handle_event("create-todo", %{"todo" => params}, socket) do

    # event handler {:noreply, socket} end
  51. You could delete the entire form - <%= f =

    form_for @changeset, "#", - phx_submit: "create-todo", - id: "new-todo" %> - - <%= text_input f, :text, placeholder: "Enter todo here" %> - <%= submit "Add todo" %> - </form>
  52. !

  53. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  54. Avoid targeting events directly

  55. Asserting text test "user can add a new todo", %{conn:

    conn} do {:ok, view, _html} = live(conn, "/todo") + rendered = view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() - assert has_element?(view, ".todo", "Decide fate of ring") + assert rendered =~ "Decide fate of ring" end
  56. Asserting text test "user can add a new todo", %{conn:

    conn} do {:ok, view, _html} = live(conn, "/todo") + rendered = view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() - assert has_element?(view, ".todo", "Decide fate of ring") + assert rendered =~ "Decide fate of ring" end
  57. Asserting text test "user can add a new todo", %{conn:

    conn} do {:ok, view, _html} = live(conn, "/todo") + rendered = view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() - assert has_element?(view, ".todo", "Decide fate of ring") + assert rendered =~ "Decide fate of ring" end
  58. None
  59. None
  60. Use has_element? in assertions

  61. Adding a new todo test "user can add a new

    todo", %{conn: conn} do {:ok, view, _html} = live(conn, "/todo") view |> form("#new-todo", todo: %{text: "Decide fate of ring"}) |> render_submit() assert has_element?(view, ".todo", "Decide fate of ring") end
  62. Example: Showing a todo's details

  63. Showing a todo's details

  64. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  65. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  66. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  67. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  68. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  69. Showing a todo's details test "selecting a todo opens todo

    details panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  70. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  71. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  72. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  73. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  74. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  75. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  76. Testing the details defmodule MyAppWeb.TodoComponentTest do use MyAppWeb.ConnCase import Phoenix.LiveViewTest

    alias MyAppWeb.TodoComponent test "renders todo with notes" do todo = create_todo(text: "Decide fate of ring", notes: "Cannot be destroyed by axe") html = render_component(TodoComponent, todo: todo) assert html =~ todo.text assert html =~ todo.notes end end
  77. Testing the right things

  78. Example: Marking a todo as complete

  79. Marking a todo as complete

  80. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  81. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  82. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  83. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  84. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  85. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  86. Assert database changes? test "user can mark todo as complete",

    %{conn: conn} do {:ok, todo} = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") updated_todo = Repo.get(MyApp.Todo, todo.id) assert updated_todo.complete end
  87. Assert database changes? test "user can mark todo as complete",

    %{conn: conn} do {:ok, todo} = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") updated_todo = Repo.get(MyApp.Todo, todo.id) assert updated_todo.complete end
  88. Write tests from the perspective of the user

  89. Marking a todo as complete test "user can mark todo

    as complete", %{conn: conn} do todo = create_todo("Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} input[name=todo]") |> render_click() assert has_element?(view, "s", "Hold council of Elrond") end
  90. But how to test this logic? def handle_event("complete-todo", %{"value" =>

    todo_id}, socket) do Todo |> Repo.get(todo_id) |> Todo.changeset(%{complete: true}) |> Repo.update!() {:noreply, assign(socket, :todos, Todos.get_all())} end
  91. But how to test this logic? def handle_event("complete-todo", %{"value" =>

    todo_id}, socket) do Todo |> Repo.get(todo_id) |> Todo.changeset(%{complete: true}) |> Repo.update!() {:noreply, assign(socket, :todos, Todos.get_all())} end
  92. Listen to your tests for signs of coupling

  93. Extract logic def handle_event("complete-todo", %{"value" => todo_id}, socket) do -

    Todo - |> Repo.get(todo_id) - |> Todo.changeset(%{complete: true}) - |> Repo.update!() + Todos.complete_todo(todo_id) {:noreply, assign(socket, :todos, Todos.get_all())} end
  94. Extract logic def handle_event("complete-todo", %{"value" => todo_id}, socket) do Todos.complete_todo(todo_id)

    {:noreply, assign(socket, :todos, Todos.get_all())} end
  95. Example: Changing how to show a todo's details

  96. Showing a todo's details

  97. Implementation Changes <span - phx-click="show-todo" - phx-value-id="<%= todo.id %>" -

    data-role="todo-text"> - - <%= todo.text %> + <%= live_patch todo.text, + to: Routes.todo_path(@socket, :show, todo), + data: [role: "todo-text"] + %> </span>
  98. Implementation Changes <span - phx-click="show-todo" - phx-value-id="<%= todo.id %>" -

    data-role="todo-text"> - - <%= todo.text %> + <%= live_patch todo.text, + to: Routes.todo_path(@socket, :show, todo), + data: [role: "todo-text"] + %> </span>
  99. Implementation Changes <span - phx-click="show-todo" - phx-value-id="<%= todo.id %>" -

    data-role="todo-text"> - - <%= todo.text %> + <%= live_patch todo.text, + to: Routes.todo_path(@socket, :show, todo), + data: [role: "todo-text"] + %> </span>
  100. Change to assert_patched/2? test "selecting a todo opens todo details

    panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() - assert has_element?(view, "#todo-#{todo.id}-details") + assert_patched(view, "/todo/#{todo.id}") end
  101. Change to assert_patched/2? test "selecting a todo opens todo details

    panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() - assert has_element?(view, "#todo-#{todo.id}-details") + assert_patched(view, "/todo/#{todo.id}") end
  102. Change to assert_patched/2? test "selecting a todo opens todo details

    panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() - assert has_element?(view, "#todo-#{todo.id}-details") + assert_patched(view, "/todo/#{todo.id}") end
  103. What does the user see? Existing behavior New behavior clicking

    on todo's text opens details panel clicking on todo's text opens details panel
  104. Test behavior, not implementat ion

  105. Test is unchanged test "selecting a todo opens todo details

    panel", %{conn: conn} do todo = create_todo(text: "Hold council of Elrond") {:ok, view, _html} = live(conn, "/todo") view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() assert has_element?(view, "#todo-#{todo.id}-details") end
  106. Writing explicit tests

  107. Example: Adding notes to a todo

  108. Adding notes

  109. Notes icon

  110. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  111. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  112. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  113. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  114. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  115. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  116. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  117. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  118. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  119. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  120. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  121. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  122. Comments are code smells. They hint at lack of clarity.

  123. Write tests in the domain of your application

  124. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  125. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") # Opening todo details view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  126. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end defp open_todo_details(view, todo) do view |> element("#todo-#{todo.id} [data-role=todo-text]", todo.text) |> render_click() view end
  127. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) # Opening editable notes view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  128. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end defp edit_notes(view, todo) do view |> element("#todo-#{todo.id}-details [data-role=edit-todo-notes]", "Edit notes") |> render_click() view end
  129. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) # Add notes view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  130. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) |> add_notes(todo, notes) assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end defp add_notes(view, todo, notes) do view |> form("#todo-#{todo.id}-details [data-role=update-todo-notes]", todo: %{notes: notes}) |> render_submit() view end
  131. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) |> add_notes(todo, notes) assert has_element?(view, "#todo-#{todo.id}-details [data-role=todo-notes]", notes) assert has_element?(view, "#todo-#{todo.id} [data-role=notes-icon]") end
  132. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) |> add_notes(todo, notes) assert has_element?(view, todo_details(todo), notes) assert has_element?(view, todo_notes_icon(todo)) end defp todo_details(todo) do "#todo-#{todo.id}-details [data-role=todo-notes]" end defp todo_notes_icon(todo) do "#todo-#{todo.id} [data-role=notes-icon]" end
  133. test "user can edit notes", %{conn: conn} do todo =

    create_todo(text: "Decide fate of the ring") notes = "We might have to cast it into mount doom" {:ok, view, _html} = live(conn, "/todo") view |> open_todo_details(todo) |> edit_notes(todo) |> add_notes(todo, notes) assert has_element?(view, todo_details(todo), notes) assert has_element?(view, todo_notes_icon(todo)) end
  134. Effective LiveView Testing Write tests

  135. Effective LiveView Testing Write tests from the perspective of the

    user (testing behavior, not implementation)
  136. Effective LiveView Testing Write tests from the perspective of the

    user (testing behavior, not implementation) in the domain of your application.
  137. tddphoenix.com

  138. @germsvel