Testing Chunked responses from a phoenix endpoint using `Plug.Test` or `Phoenix.ConnTest`

I am implementing Server Sent events in a phoenix project. This is easy enough to do by using plug directly. I am following this gist.

However I am not sure of the best way to test such an endpoint. I could use HTTPoison Async Requests and connect to the test server but I would rather just test the conn object using Plug.Test or Phoenix.ConnTest. Is this possible are there any examples out there on how to do this.

2 Likes

I researched this a bit, and it’s indeed far from being trivial. From what I found, when we call Plug.Conn.chunk(conn, chunk), plug calls conn.adapter.chunk. From there, the chunk should be sent to the server (e.g. cowboy) for further handling. The conn is not aware of the chunk anymore.

In tests, the adapter is Plug.Adapters.Test.Conn, and its chunk function reply with {:ok, payload}. It doesn’t really matter what the payload is, because Plug.Conn.chunk is probably called from a process that is not going to finish / return this state in any way. This process communicate messages back to the client forever, in a loop.

So, in the case of the test, I see no way to get the chunk out of the process, at least not in a “conventional” way. So, I tried to solve this by mocking Plug.Conn.chunk to send a the chunk to the test process, and there I assert_receive for it. You can see an example here. It’s not the cleanest solution, I know… What do you think?

1 Like

I tried to get the method by @Nagasaki45 but I could not get it to work. I also tried mocking Plug.Conn.chunk which also didn’t work for me I ended up doing this.

I created a function with minimal side effects and used that to run the chunking.

defmodule MyApp.ControllerUtils do
    use MyaAppWeb, :controller

    @callback chunk_to_conn(map(), String.t()) :: map()
    def chunk_to_conn(conn, current_chunk) do
        conn |> chunk(current_chunk)
    end
end

Now in my controller, i call chunk_to_conn(conn, current_chunk) which I can mock in test to give me the chunks like this
In my test support

Mox.defmock(MyAppWeb.ControllerUtilsMock, for: MyAppWeb.ControllerUtils)

And in my test

defmodule MyApp.MyTest do
   import Mox
   defp chunked_response_to_state(chunk, pid) do
    current_chunk = Agent.get(pid, &Map.get(&1, :chunk_key))
    Agent.update(pid, &Map.put(&1, :csv, current_chunk <> chunk))
   end
   setup do
       MyApp.ControllerUtilsMock
         |> stub(:chunk_to_conn, fn _, chunk -> chunk |> chunked_response_to_state(agent_pid) end)
        {:ok, %{agent_pid: agent_pid}}
   end
   test "my test", state do
       build_conn.get(somepath)
       whole_chunks = Agent.get(state.agent_pid, &Map.get(&1, :chunk_key))
   end

end
1 Like