Phoenix LiveView Testing

2023-11-06

Phoenix LiveView is a pleasure to work with, and the testing capabilities in LiveView add one more benefit to using it.

What is Phoenix and Phoenix LiveView?

Phoenix Framework is a web framework built on top of the Elixir programming language. A relatively new addition to the framework is the Phoenix LiveView library. It is designed to simplify the development of interactive and real-time web applications without the need for extensive JavaScript code.

It leverages WebSockets to provide server-rendered HTML pages that automatically update and handle user interactions in real time. The updates from the server to the client are heavily optimised to minimise the amount of data sent over the wire.

The state is kept on the server, and there’s no need to build an API to retrieve the state from the server. This is a significant benefit as it reduces the code needed to build the application.

JavaScript libraries can bring extra functionality when required, and you’re not locking yourself out of using JavaScript.

LiveView Testing

LiveView testing allows you to perform integration testing, where you can test the entire application stack, including the server-side code, client-side interactions, and real-time functionality. This helps identify issues that may arise from the interaction of different components.

Having worked with browser-based testing for years and being familiar with its challenges, I find it a pleasure to work with the Phoenix Framework and, specifically, Phoenix LiveView and its testing capabilities.

LiveView tests are not a stand-alone testing tool but require you to build a LiveView application to use them. LiveView tests are another reason to build on this excellent and robust platform.

Test Speed in LiveView vs. Browser-based

Everyone wants fast tests, although it’s usually not an issue early on in a project. Each new test adds some build time, and depending on the test type, this can be in the range of 0.1 to 2 seconds using the tools available for browser-based tests. This can add up, and soon, the pipeline takes 10 minutes or more to run.

Is it fair to compare LiveView tests to browser-based tools? Yes, I believe so. While LiveView tests do not use a browser, they cover much of the same functionality. When looking at the test pyramid, the tests should be run on as low a level as possible. LiveView tests are one layer in the pyramid, and what is tested on this level doesn’t need to be retested on a higher level, or can be tested with a light touch.

LiveView Test Result Example!

Robustness

In addition to the speed, LiveView tests are also very reliable, which is not always the case for browser-based testing. Browser-based tests have been known to be flaky, although tools such as Playwright have dramatically improved the situation in recent years.

Testing with the Full Stack

One critical point to understand with LiveView testing is that it can exercise the full stack (LiveView component tests also exist, but this post is about testing the views).

Pages can be rendered, and the element’s presence or contents can be asserted. What’s excellent about LiveView testing is that you can also test the logic and what happens when the user takes an action on the page.

Similar tests can be written as end-to-end tests in any platform using, for example, Playwright, Cypress or a Selenium-based tool. The difference is that LiveView tests are blazingly fast, and by that, I mean that I can run 50 tests that render pages, navigate, filter items in a list, and verify that they work in 0.5 seconds. This is faster than launching the browser for the first test in many other systems. In addition to the speed, an important thing to understand is that LiveView tests can cover the whole system end-to-end. A page is rendered, fetching the required data from the database usually.

Are there any downsides? These tests do not use a browser to render the content. To ensure the application behaves the same in the browser, you should also have a couple of end-to-end tests to test browser compatibility issues. The browser compatibility situation has significantly improved in recent years as most browsers use the same rendering engines.

This is fine, though, according to the test pyramid. If we can cover most of the system’s functionality in seconds and not minutes, we can afford to add some browser-based tests in the pipeline.

React Component tests are also fast. They render the component with a state passed in the test, which means it doesn’t come from the backend and thus only tests the component. This is not a surprise as this is the purpose of component testing, but it leaves a gap in tests often covered by end-to-end tests.

How Does a Test Look Like?

This is a basic test example for opening a page and asserting on some content on the page, in this case service names. The same rules apply as with other testing tools, i.e. specific parts of the code can be abstracted and converted into helper functions when the need for that arises.

test "it shows a list of services", %{conn: conn} do
  {:ok, view, html} = live(conn, "/services")
  assert html =~ "Services"

  service_list =
    view
    |> element("[data-test-id=service-list]")
    |> render()
    |> Floki.find(".service-name")
    |> Enum.map(&Floki.text/1)

  assert service_list === [
           "Kick-start Test Automation",
           "Conversion to Modern Tech",
           "Ongoing Support"
         ]
end

The next example shows a case where the user clicks on a filter on the page, and expects the displayed content to be updated. This is a common scenario in many applications, and it’s easy to test with LiveView.

test "it can filter blog posts by tags", %{conn: conn} do
  {:ok, view, _html} = live(conn, "/blog")

  rendered =
    view
    |> element("a", "specific_tag")
    |> render_click()

  titles =
    rendered
    |> Floki.find("h2")
    |> Enum.map(&Floki.text/1)
    |> Enum.map(&String.trim/1)

  tags =
    rendered
    |> Floki.find("span.tag")
    |> Enum.map(&Floki.text/1)
    |> Enum.map(&String.trim/1)

  assert titles === ["Specific Blog Title"]
  assert tags === ["specific_tag"]
end

Conclusion

Taking advantage of LiveView testing capabilities is a game-changer for a project. The ability to cover a lot of the functionality with fast and robust tests is a requirement in modern software projects. The tools provided in Phoenix LiveView make testing much more effortless.