Graeme

Graeme

Hi I’m just starting out with Elixir and Phoenix so I want to see if I’m going about this the right way. Let’s say I have a Phoenix Channel that acts as a chat client. All users that are in the same room/topic can either send messages directly to one another, or “shout” them so that everyone gets the message. I can make a test for shout like this:

# replaced some unimportant params with ... to keep this small 
test "shout" do
  assert {:ok, _, socket1} = socket("", %{}) |> subscribe_and_join(...)
  assert {:ok, _, _socket2} = socket("", %{}) |> subscribe_and_join(...)

  push(socket1, "shout", "hi")

  assert_broadcast("shout", %{msg: "hi"})
  assert_broadcast("shout", %{msg: "hi"})
end

But the issue is that this only tests that the message got sent twice. If somehow it got broadcasted twice but only to one of the clients the test would still pass. Since clients are identified by their pid it looks like the only way to differentiate is to spawn a new process for each client. I really want the test to be this:

  1. client1 joins room
  2. client2 joins room
  3. client1 shouts “hi”
  4. client1 receives “hi” shout and client2 receives “hi” shout (order not important)

It’s sort of ugly but I can do that like this:

test "shout2" do
  task1 = Task.async(fn ->
    assert {:ok, _, socket1} = socket("", %{}) |> subscribe_and_join(...)
    receive do :proceed -> :ok end # make sure client2 has joined before shouting
    push(socket1, "shout", "hi")
    assert_broadcast("shout", %{msg: "hi"})
  end)

  task2 = Task.async(fn ->
    assert {:ok, _, _socket2} = socket("", %{}) |> subscribe_and_join(...)
    send(task1.pid, :proceed)
    assert_broadcast("shout", %{msg: "hi"})
  end)

  Enum.map([task1, task2], &Task.await/1)
end

That works, but it seems like it could become a pain to write tests like this especially since my real use case is more complicated. To simplify things I made a GenServer called AsyncSocket that allows me to write the test like this:

test "shout3" do
  s1 = AsyncSocket.create_and_join(...)
  s2 = AsyncSocket.create_and_join(...)

  AsyncSocket.push s1, "shout", "hi"
  AsyncSocket.assert_broadcast s1, "shout", %{msg: "hi"}
  AsyncSocket.assert_broadcast s2, "shout", %{msg: "hi"}
end

AsyncSocket.push and AsyncSocket.assert_broadcast end up calling the GenServer so that those functions can run on the same process that owns the socket.

So my question is this: Am I going in the right direction? I feel like I must be reinventing the wheel but I can’t figure out how to do this cleanly using the libraries built into Phoenix.

EDIT: here is a gist with the source of AsyncSocket in case its not clear what it does.

Showing Posts 1 to 3

dom

dom

If you’re not using intercept, I would say there’s no need to test with multiple channels. A broadcast that’s not intercepted will go to all clients by definition.

If you are using intercept, then you need to use assert_push instead of assert_broadcast. Otherwise the channel could filter the broadcast in its handle_out callback and the test would not catch it.

You can avoid using separate processes by testing the two properties separately:

test "shouts are broadcasted exactly once" do
  assert {:ok, _, socket} = socket("", %{}) |> subscribe_and_join(...)

  push(socket, "shout", "hi")
  assert_broadcast("shout", %{msg: "hi"})
  refute_broadcast("shout", %{msg: "hi"})
end

test "shouts are pushed to all clients" do
  assert {:ok, _, socket1} = socket("", %{}) |> subscribe_and_join(...)
  assert {:ok, _, _socket2} = socket("", %{}) |> subscribe_and_join(...)

  push(socket1, "shout", "hi")

  assert_push("shout", %{msg: "hi"})
  assert_push("shout", %{msg: "hi"})
end
Graeme

Graeme OP

Yes I will be using intercept (but I did not do iot in the test code I wrote for these examples) so I guess I oversimplified. I didn’t know I need to use assert_push in that case. Thanks for clearing that up.

I was trying to make this representative of a more general problem when you need to assert that specific clients receive specific messages. For example, if you could send a group message to a specific list of people and you needed to test that the correct clients received it and the others did not receive it then you would need multiple processes, right?

dom

dom

It depends how your app works, but in general you only need one process (the receiver) to test the filtering. The test itself acts as the sender using broadcast_from.

If you really need to have multiple clients in parallel, then I think the AsyncSocket sounds reasonable.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
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
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
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews