marc0s

marc0s

How to keep a websockex client connected?

Hi,

I’m learning elixir and the current use case I’m now playing with is a websocket server with plug_cowboy and a client with websockex. Server is working nice if I test with websocat or other clients.

My main issue is that while coding a simple test case where the client sends a message and the test finishes (ends the process, as I understand, which may be wrong) before actually receiving the response. I suspect I’m missing some kind of loop that keeps reading the socket for incoming messages instead of just sending a message and finishing the process [because no more code is left to be run].

My code is as follows, test client first:

  use WebSockex
  require Logger
  
  def start(url, state) do
    WebSockex.start(url, __MODULE__, state)
  end

  def request(client, message) do
    Logger.info("Sending request: #{inspect message}")
    WebSockex.send_frame(client, {:text, message})
  end
  
  def handle_connect(_conn, state) do
    Logger.info("Connected")
    {:ok, state}
  end
  
  def handle_frame({:text, msg}, state) do
    Logger.info "Received a message: #{inspect msg}"
    {:ok, state}
  end

  def handle_cast({:send, {type, msg} = frame}, state) do
    IO.puts "Sending #{type} frame with payload #{inspect msg}"
    {:reply, frame, state}
  end

  def terminate(_reason, _state) do
    IO.puts("terminate")
    exit(:normal)
  end
end

And the test:

    {:ok, pid} = Client.start("ws://localhost:4444/", %{})
    Client.request(pid, Jason.encode!(%{msg: "hi there"}))
    # something missing here to keep Client connected and later call Client.stop
  end

Thanks in advance!

Marked As Solved

alvises

alvises

The Client is asynchronous, so when you make a request/2 the process of that test ends without waiting for any answer, bringing everything down (and closing the connection). So, the quickest way to see the log, to see if your experiment works, is to use iex (the elixir interactive console) and start the client there.

If you really need to make a real unit test, the frames the Client receives from the server are internal. You shouldn’t test directly those messages, instead I would test the client’s interface.

Ok, so let’s say you want to keep everything asynchronous and, as part of the implementation of your Client, you want that the client forwards to a process each websocket frame:

defmodule Client do
   ...
   def start_link(url, send_to_pid) do
    WebSockex.start_link(url, __MODULE__, %{send_to_pid: send_to_pid})
  end

  def handle_frame({:text, msg}, %{send_to_pid: pid}=state) do
    Logger.info "Received a message: #{inspect msg}"

    send pid, {:websocket_msg_received, self(), msg}

    {:ok, state}
  end

  ...
end

test "receives message from the client when a websocket text frame is received" do
    {:ok, pid} = Client.start_link("ws://localhost:4444/", self())
    Client.request(pid, Jason.encode!(%{msg: "hi there"}))

    assert_receive {:websocket_msg_received, ^pid, _}
end

When you start the client, you pass self(), which is the test process pid. When the client process receives a websocket frame, it sends a message to send_to_pid. With assert_receive the test waits that the message is received.

As you can see in handle_frame/2, the client sends also its pid (self()) as part of the message. In this way we can pattern match it with assert_receive to be sure that the pid is the same of the client we started.

PS: use start_link so what you spawn is a linked process. In this way when test exists it brings down your client process.

Note that a GenServer started with start_link/3 is linked to the parent process and will exit in case of crashes from the parent
(GenServer — Elixir v1.16.0)

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
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
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement