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.20.2)

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
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
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement