marc0s

marc0s

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!

Showing Posts 1 to 4

alvises

alvises

If I understood correctly, you have a local websocket server, made with cowboy and plug_cowboy. After you call Client.request/2, you expect that the client receives a websocket frame from the server right? What exactly do you want to test, which assertion?

marc0s

marc0s OP

(Currently) The server is just an echo server, so I want to assert in the test that the server sent back the same the client sent first. By looking at the server logs, the message is received and replied, but (coming from other languages) client side I’d expect the client to remain connected to the websocket and see how the reply is logged,at least to stdout :slight_smile:

I’m probably missing something very basic here, to the extent that my problem may not be understood. Please ask me to clarify whatever makes no sense!

Thanks.

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)

marc0s

marc0s OP

Thank you so much for the detailed explanation. Really appreciated :slight_smile:

I got it working with your indications. Still need to grasp what seems to me the “all is a process” motto and so I need to be sending messages all along to interact with the different elements.

Best regards.

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews