Search code examples
graphqlerlangelixirphoenix-frameworkchannel

Elixir/Phoenix Testing Graphql Subscription ChannelCase and SubscriptionCase Set Up


For my elixir project I have a following subscription in my lib/graphql_user_web/schema/subscriptions/user.ex:

defmodule GraphqlUserWeb.Schema.Subscriptions.User do
  use Absinthe.Schema.Notation

  object :user_subscriptions do
    field :created_user, :user do
      trigger :create_user, topic: fn _ ->
        "new_user"
      end

      config fn _, _ ->
        {:ok, topic: "new_user"}
      end
    end
  end
end

So far it has been working well on localhost:4000/graphiql.

I just want to add unit test for it.

Here's my setup of ChannelCase in test/support/channel_case.ex:

defmodule GraphqlUserWeb.ChannelCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      use Phoenix.ChannelTest
      import Absinthe.Phoenix.SubscriptionTest

      @endpoint GraphqlUserWeb.Endpoint
    end
  end

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(GraphqlUser.Repo)

    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(GraphqlUser.Repo, {:shared, self()})
    end

    {:ok, socket} = Phoenix.ChannelTest.connect(GraphqlUserWeb.UserSocket, %{})
    {:ok, socket: Absinthe.Phoenix.SubscriptionTest.join_absinthe(socket)}
  end
end

And here's my SubscriptionCase setup in test/support/subscription_case.ex:

defmodule GraphqlUserWeb.SubscriptionCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      import Phoenix.ChannelTest
      use GraphqlUserWeb.ChannelCase
      use Absinthe.Phoenix.SubscriptionTest,
       schema: GraphqlUserWeb.Schema
      setup tags do
        {:ok, socket} = Phoenix.ChannelTest.connect(GraphqlUserWeb.Endpoint, %{})
        {:ok, socket: Absinthe.Phoenix.SubscriptionTest.join_absinthe(socket)}
        {:ok, %{socket: socket}}
      end
    end
  end
end

And I have a pretty simple/basic test in test/graphql_user_web/schema/subscriptions/user_test.ex:

defmodule GraphqlUserWeb.SubscriptionTest do
  use GraphqlUserWeb.SubscriptionCase

  alias GraphqlUser.Factory

  describe "user subscriptions" do
    setup do
      {:ok, user: Factory.insert(:user)}
    end

    test "receives a created_user subscription event", %{socket: socket} do
      # Subscribe to the "created_user" subscription
      ref = push_doc(socket, """
      subscription {
        createdUser {
          id
          name
          email
        }
      }
      """)
      assert_reply ref, :ok, reply
      IO.inspect(reply)
    end
  end
end

The test basically cannot run and throws error:

  1) test user subscriptions receives a created_user subscription event (GraphqlUserWeb.SubscriptionTest)
     test/graphql_user_web/schema/subscriptions/user_test.exs:11
     ** (UndefinedFunctionError) function Phoenix.ChannelTest.connect/2 is undefined or private. However, there is a macro with the same name and arity. Be sure to require Phoenix.ChannelTest if you intend to invoke this macro
     stacktrace:
       (phoenix 1.7.18) Phoenix.ChannelTest.connect(GraphqlUserWeb.UserSocket, %{})
       (graphql_user 0.1.0) test/support/channel_case.ex:20: GraphqlUserWeb.ChannelCase.__ex_unit_setup_0/1
       (graphql_user 0.1.0) test/support/channel_case.ex:1: GraphqlUserWeb.ChannelCase.__ex_unit__/2
       test/graphql_user_web/schema/subscriptions/user_test.exs:1: GraphqlUserWeb.SubscriptionTest.__ex_unit__/2

I tried doing import Phoenix.ChannelTest instead of use Phoenix.ChannelTest. Still same error. Can anyone point out where I'm being dumb? Thanks! I think it's my setup of ChannelCase and/or SubscriptionCase.


Solution

  • TLDR: you should add the following:

    defmodule GraphqlUserWeb.ChannelCase do
      use ExUnit.CaseTemplate
      
      require Phoenix.ChannelTest # <= here
    
      using do
    

    As for the longer explanation, there are two different lexical scopes at work here:

    • the scope of ChannelCase, which was calling a macro from Phoenix.ChannelTest so it needs to require it first
    • the scope of SubscriptionTest or any module using ChannelCase, which is where ChannelCase's using block will be expanded

    use is for injecting code in other modules, but it won't affect the module defining the code being injected.