Introducing PhoenixTest
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. ✨
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!