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
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
GenServerstarted withstart_link/3is linked to the parent process and will exit in case of crashes from the parent
(GenServer — Elixir v1.20.2)
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








