germsvel

germsvel

PhoenixTest - a unified way of writing feature tests for LiveView and static pages

PhoenixTest provides a unified way of writing feature tests – regardless of whether you’re testing LiveView pages or static pages.

It also handles navigation between LiveView and static pages seamlessly. You don’t have to worry about what type of page you’re visiting or navigating to. So, you can test a flow going from static to LiveView pages and back without having to worry about the underlying implementation.

Just write your tests from the user’s perspective. :sparkles:

Why create PhoenixTest?

With the advent of LiveView, I find myself writing less and less JavaScript.

Sure, there are sprinkles of it here and there, and there’s always the occasional need for something more.

But for the most part, if I’m going to build a page that needs interactivity, I use LiveView. If I’m going to write a static page, I use regular controllers + views/HTML modules.

The problem is that LiveView pages and static pages have vastly different testing strategies.

If I use LiveView, I can write my tests like this:

{:ok, view, _html} = live(conn, ~p"/")

html =
  view
  |> element("#greet-guest")
  |> render_click()

assert html =~ "Hello, guest!"

But if I want to test a static page, we have to resort to controller testing:

conn = get(conn, ~p"/greet_page")

assert html_response(conn, 200) =~ "Hello, guest!"

That means I don’t have ways of interacting with static pages at all!

What if I want to submit a form or click a link? And what if a click takes me from a LiveView to a static view or vice versa?

A Unified way of writing feature tests

So, I wanted a unified way of writing feature tests – regardless of whether or not you’re testing LiveView pages or static pages.

And that’s what PhoenixTest does!

Imagine having a workflow that starts on a static page. We click a link and are taken to a LiveView page. Then, we fill a LiveView form (which could even have its phx-change triggered), and then we submit the form. Finally, we assert that what we expect to see is present.

All of that in a succinct, pipe-friendly way!

test "can create a user", %{conn: conn} do
  conn
  |> visit("/")  # <- could be LiveView or static page
  |> click_link("Users") # <- navigate between LiveViews and static pages
  |> fill_form("#user-form", name: "Aragorn", email: "aragorn@dunedain.com")
    # ^ will trigger `phx-change` if present
  |> click_button("Create") # <- submits LiveView forms and regular forms
  |> assert_has(".user", "Aragorn") # <- provides better error messages
end

Improving assertions

Then there’s the problem of assertions.

Because LiveView and controller tests use =~ for assertions, the error messages aren’t very helpful when assertions fail.

After all, we’re just comparing two blobs of text – and trust me, HTML pages can get very large and hard to read as a blob of text in your terminal.

LiveView tries to help with the has_element?/3 helper, which allows us to target elements by CSS selectors and text.

But, unfortunately, it still doesn’t provide the best errors.

has_element?/3 only tells us what was passed into the function. It doesn’t give us a clue as to what else might’ve been on the page – maybe we just made a small typo and we have no idea!

With PhoenixTest.assert_has/3 and PhoenixTest.refute_has/3, we can provide better errors when assertions fail.

How did you write feature tests before?

In the past, I would’ve used Wallaby to write feature tests.

But since I’m not writing as much JavaScript, I don’t feel the need for something so heavy – requiring chromedriver and slowing down our tests.

Instead, I’d like to have something more akin to what LiveView tests brought us – tests that act as though there’s a driver, but they mostly parse HTML and allows us to make assertions about it.

That’s where PhoenixTest shines.

Of course, without something like chromedriver, we cannot test JavaScript. So, PhoenixTest remains blissfully ignorant of it. If you need that, check Wallaby.

But if you’re looking for something to write fast feature tests for your LiveView pages and static pages – and something that is (hopefully) a pleasure to write – check out PhoenixTest!

Links

Most Liked

germsvel

germsvel

The library has been consistently improving since its introduction.

Today I published the biggest change (from an API perspective) on how we fill form.

PhoenixTest v0.2.13 deprecates two form helpers :disappointed_face: but introduces new ones to help us fill forms with labels. :star_struck:

I know it’s a pain to change this (I don’t change APIs lightly), but I think the changes improve our tests.

Change this: :backhand_index_pointing_down:

session
|> fill_form("form", user: %{
  name: "Aragorn",
  admin: true,
  country: "Arnor"
})

To this: :backhand_index_pointing_down:

session
|> fill_in("Name", with: "Aragorn")
|> check("Admin")
|> select("Arnor", from: "Countries")

Read the full upgrade guide: Upgrade Guides — PhoenixTest v0.9.1

germsvel

germsvel

PhoenixTest v0.3.0 is now out! :mega:

  • Removes deprecated code

  • Adds unwrap for an escape hatch :hatching_chick:

  • Handles buttons submitting forms when not nested in the form

  • and more!

Grab it while it’s hot :fire:

:gear: phoenix_test | Hex

:books: PhoenixTest — PhoenixTest v0.3.0

soyjeansoy

soyjeansoy

Wish they’d consider that idea

sodapopcan

sodapopcan

Holy heck this is AWESOME! Thank you!!!

Schultzer

Schultzer

I think follow redirect would be awesome, but even better would be to kinda merge all of this with phoenix and phoenix live_view into phoenix.

Where Next?

Popular in Announcing Top

dimamik
Vault is a lightweight Elixir library for immutable data storage within a process subtree. Due to Elixir’s actor model nature, it’s comm...
New
mikehostetler
I’m falling in love with the Req Plugin pattern. It has limits, but when it works - it’s good. I ported over my Tesla based Fly Machines...
New
jarlah
Testcontainers Testcontainers is an Elixir library that supports ExUnit tests, providing lightweight, throwaway instances of common datab...
New
mikehostetler
LLM DB - LLM Model Metadata Package This package was extracted out of the ReqLLM project. LLM DB is a model metadata catalog with fast, ...
New
jallum
Turbopuffer - Elixir client for vector and full-text search I’m excited to share Turbopuffer, a new Elixir client library for the Turbop...
New
bentanweihao
Here’s a preview: Here’s the link to download: GenServer cheatsheet Tell me what you think!
New
munksgaard
flakify is an igniter installer that allows you to quickly get a Nix flake-based development shell up and running for your Elixir/Phoenix...
New
leandrocp
MDEx is a fast and extensible Markdown parser and formatter. Fast Leverage Rust to parse, manipulate and render documents using: comra...
New
Asd
Hi, I am happy to release the Repatch library for mocking and patching implementation in tests and anywhere else. It brings new possibili...
New
taro
I took lessons from the last discussion and cobbled together an example as a proof-of-concept. Mar demonstrates a Flask-like web dev int...
New

Other popular topics Top

Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New

We're in Beta

About us Mission Statement