gianthamster

gianthamster

I’m working on a multiple-choice quiz application that uses LiveView for its interface. The questions are randomly selected from a larger set, and then stored in the socket’s assigns, along with their answers.

When testing the LiveView, I’d like to look at a question contained in the assigns (and along with it, its answer), so I can then test what happens when a right answer or wrong answer is selected by the user. However, I don’t see an easy way to access the socket and its assigns from the test case itself – just the HTML of the rendered view.

Am I missing a simple way to get at socket.assigns in a Phoenix.LiveViewTest, or will I need to mock the quiz interface so it always returns something non-random in the test environment?

Showing Posts 1 to 10

crisefd

crisefd

Are you testing the code in your *_live.ex file or are you testing a *_view.ex file?

gianthamster

gianthamster OP

In my *_live.ex file. The tests generally start with {:ok, view, html} = live(conn, "/quiz/") and then an assert render_click.

crisefd

crisefd

Isn’t the socket’s assigns avaiable from conn object ?

gianthamster

gianthamster OP

Interesting. The conn before the {:ok, view, html} = live(conn, "/quiz/") call has empty assigns, and the live(conn, "/quiz/") call doesn’t return an updated conn, just the view and the rendered html, so I was going to say the conn object wasn’t even available to the test.

But if I break up the call to live by doing a conn = get(conn, "/quiz") followed by {:ok, view, html} = live(conn), then that intermediate conn does indeed have the data I’m looking for in its assigns.

Thanks for the poke, I’d been assuming conn.assigns both wasn’t available and wasn’t related to the socket.assigns anyway.

I’m still not sure how to access the assigns later on, after they’ve been changed by some handle_event calls, but that doesn’t matter for this test.

chrismccord

chrismccord

Creator of Phoenix

Note that you should test the results of the LV rendered content rather than the assigns. Reaching into the assigns is internal state and will lead to a brittle test. So using the render_* functions and asserting on the expected side effects is the way to go :slight_smile:

13
Post #5
gianthamster

gianthamster OP

I do get that the structure of assigns is a lot more likely to change than the contents of the view, and that other people working on the same code might not expect the tests to depend on the structure of assigns. But since we’re here, here’s my passing test:

  test "correct answer clicked", %{conn: conn} do
    conn = get(conn, "/quiz")
    {:ok, view, _html} = live(conn) 

    correct_answer = conn.assigns.correct_answer
    expected = "Congratulations! You got it right!"

    assert render_click(view, :answer, %{option: correct_answer}) =~ expected
  end

The value of correct_answer is determined by a quiz-generating call during the mount and is completely random – while the answer to ‘What sound does a cat make?’ will always be ‘Meow’, sometimes that’ll be the first answer, sometimes that’ll be the second answer, etc.

I don’t consider myself particularly skilled here, so this is my chance to learn something – instead of doing what I’ve done, how would you test something like this? Mock the quiz-generating function? Pass an anonymous answer-sorting function to the quiz-generating function as a variable, and use a deterministic one in test? Click on every link, add up the results, and assert that the ‘correct answer’ result happened once and only once? Change the implementation so the correct answer isn’t in the assigns at all, but fetched from the backend after every answer? I considered all of these instead of dipping into the assigns, but didn’t love any of them – the increased complexity didn’t seem worth it.

DavidRawk-Blake

DavidRawk-Blake

Wouldn’t this violate functional programming. We’d be testing the side-effects of a function not the input output.

Testing a handle_event should take data in and data out. Regardless of the content of the template

sodapopcan

sodapopcan

TL;DR, LiveViewTest’s purpose is to test the simulated effects of network requests.


LiveView tests—while not quite end-to-end tests—are meant to test what the user sees, not the internals. We can see this with the form/3 function not allowing us to fill in hidden fields. We also get a good hint with all the render_ functions that LiveViewTest gives us that are always called just before an assertion. At this abstraction level, the fact that the returned compiled templates are the result of side-effects isn’t important.

You can even see this with how LV tests are written:

{:ok, lv, _html} = live(conn, "/")

lv
|> element("a")
|> render_click()

lv # <-- `lv` here is the result of the side effect of `render_click`
|> form("form", %{name: "Bob"})
|> render_submit()

While we know the lv variable itself and its value have not been mutated, the process belonging to the pid it holds has! (or like, you know, it’s been “functionally updated” but for all intents and purposes we could say it has been mutated).

lccezinha

lccezinha

If you want a hack approach to access assigns I think you can have it by the conn struct or doing:

:sys.get_state(lv.pid).socket.assigns if I am not wrong.

But that is not a good approach since LV test you should follow an approach simulating the user interaction with your app using the render_* functions.

egeersoz

egeersoz

Template changes resulting from an event are not side effects. They are the main effect, i.e. the final action of the live action. The template reflects the changes that occur as a result of the handle_event. How exactly those changes happen shouldn’t be relevant to your tests.

To come at it from a different perspective: When a user clicks a button on the page, they are concerned with what changes they see as a result. They do not care what your assigns are named or what goes on inside the handle_event function. Therefore, neither should your tests.

Suppose you have template that shows a list of ToDo items with their count in the top left corner, and there’s a button that lets you add a todo item with a given name. A proper test would be to call render_click on the ‘create’ button and assert that the ToDo list contains a new item with the given name, and assert that the new count appears somewhere on the page. A brittle test would be to test that assigns.todo_items contains the new todo item and assigns.to_do_count is original + 1. The former is what the user cares about. The latter is an implementation detail, and such a test will break if, for example, the assigns are renamed during a refactor.

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews