Skip to content

Invalid Type with ChannelTest Sockets聽#5509

Closed

Description

@aj-foster

Environment

  • Elixir version (elixir -v): 1.15.2
  • Phoenix version (mix deps): 1.7.6
  • Operating system: macOS 13.4.1

Actual behavior

Hello 馃憢馃徏

This relates to the intersection of production code and test code, so it may not be an actual issue.

The type Phoenix.Socket.t() specifies the types of most fields, however Phoenix.ChannelTest initializes sockets with several fields that are either missing (such as channel_pid or have a different type (like transport). As a result, the following code (from the ChannelTest module documentation) is caught by Dialyzer as having no return:

{:ok, _, socket} =
  socket(UserSocket, "user:id", %{some_assigns: 1})
  |> subscribe_and_join(RoomChannel, "room:lobby", %{"id" => 3})

# or...

{:ok, socket} = connect(UserSocket, %{"some" => "params"}, %{})
{:ok, _, socket} = subscribe_and_join(socket, "room:lobby", %{"id" => 3})

At the application level, Dialyzer generally doesn't give a descriptive error beyond "has no local return", however if you copy the library code in place of the calls, it warns about the call to Phoenix.Channel.Server.join/4:

The function call will not succeed.

Phoenix.Channel.Server.join(
  _socket :: %Phoenix.Socket{
    :assigns => _,
    :channel => nil,
    :channel_pid => nil,
    :endpoint => atom(),
    :handler => _,
    :id => _,
    :join_ref => nil,
    :joined => false,
    :private => %{},
    :pubsub_server => _,
    :ref => nil,
    :serializer => Phoenix.ChannelTest.NoopSerializer,
    :topic => nil,
    :transport => {Phoenix.ChannelTest, pid()},
    :transport_pid => pid()
  },
  MyAppWeb.RoomChannel,
  _message :: %Phoenix.Socket.Message{
    :event => <<_::64>>,
    :join_ref => nil,
    :payload => %{},
    :ref => integer(),
    :topic => <<_::56>>
  },
  [
    {:starter,
     (_, _, _ -> {:error, _} | {:ok, :undefined | pid()} | {:ok, :undefined | pid(), _})},
    ...
  ]
)

breaks the contract
(Phoenix.Socket.t(), module(), Phoenix.Socket.Message.t(), :elixir.keyword()) ::
  {:ok, term(), pid()} | {:error, term()}

Notably, I discovered this mismatch only because I chose to call ChannelTest functions in the setup block contained within my application's ChannelCase module. Normally, Dialyzer would not have the chance to catch this code in test files.

Expected behavior

It seems unlikely that the Phoenix.Socket.t() type should be expanded to include the socket created in ChannelTest (except possibly for fields like transport), but on the other hand I wonder if it is even feasible for the fake Socket to be "complete" in this context. Since these functions are generally called in test files anyway, maybe it's not worth exploring further.

鉂わ笍

Activity

  1. chrismccord commented on Jul 7, 2023

    @chrismccord
    Member

    I'd happily review a PR that fixes this, but I'm not a dialyzer user myself so it's not something I'll be able to prioritize. Thanks!

  2. osazemeu commented on Oct 7, 2026

    @osazemeu
    Contributor

    I opened #6870 to fix this. ChannelTest now keeps its supervisor in socket.private instead of in a transport tuple, and topic and channel_pid are typed as nilable in Phoenix.Socket.t. With both changes, a minimal repro goes from 4 Dialyzer errors to 0.

  3. added 2 commits that reference this issue on Oct 7, 2026
    cf8b44b
    dbd3d0a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions