Skip to content

Add process-local endpoint configuration via PhoenixTest.Config#307

Open
nbernardes wants to merge 1 commit intogermsvel:mainfrom
nbernardes:nbernardes/process-local-endpoint-config
Open

Add process-local endpoint configuration via PhoenixTest.Config#307
nbernardes wants to merge 1 commit intogermsvel:mainfrom
nbernardes:nbernardes/process-local-endpoint-config

Conversation

@nbernardes
Copy link
Copy Markdown

@nbernardes nbernardes commented Apr 2, 2026

Problem

When using phoenix_test, the endpoint needs to vary depending on which test suite is running. In umbrella apps or projects with multiple Phoenix endpoints, Application.get_env(:phoenix_test, :endpoint) is global, so it can't differ per test suite and isn't safe when running tests in parallel. put_endpoint/2 solves this for standard PhoenixTest (it sets conn.private), but external drivers like phoenix_test_playwright read the endpoint from Application config directly since they don't use Plug.Conn.

Solution

Introduce PhoenixTest.Config.put_endpoint/1 which stores the endpoint in the process dictionary. Since each ExUnit test runs in its own process, different test suites can set different endpoints without interfering with each other.

Endpoint resolution chain:

  1. conn.private[:phoenix_endpoint] (per-conn)
  2. Process dictionary (per-test-process) — new
  3. Application.get_env(:phoenix_test, :endpoint) (global fallback)

put_endpoint/2 now also sets the process-level config, so both approaches stay in sync.

Usage

  # In each ConnCase/FeatureCase:
  setup do
    PhoenixTest.Config.put_endpoint(MyAppWeb.Endpoint)
    {:ok, conn: Phoenix.ConnTest.build_conn()}
  end

Fully backward-compatible - existing config :phoenix_test, :endpoint setups continue to work unchanged.

@frm
Copy link
Copy Markdown

frm commented Apr 9, 2026

Hey @germsvel, is it possible to take a look at this? particularly useful for umbrellas with multiple endpoints

Copy link
Copy Markdown
Owner

@germsvel germsvel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for opening this PR! 🙏

Sorry it took so long to review. I didn't even realize this was open (somehow). But I've also been very busy. So, thanks for the patience.

I'm not opposed to introducing this. I don't typically work in Umbrella apps, but I can see what you mean by needing it with phoenix test playwright and umbrella apps.

I left a few suggestions that I think would make it better. If something is unclear, let me know. Happy to chat more about it.

test "raises when no endpoint configured" do
Process.delete(:phoenix_test_endpoint)
original = Application.get_env(:phoenix_test, :endpoint)
Application.delete_env(:phoenix_test, :endpoint)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test makes it so we can't run this in async: true mode (since we're updating application config). Sadly, we may have to set async: false

Comment thread lib/phoenix_test.ex
drivers can resolve the endpoint too.

2. You can set it at compile time in `config/test.exs`:
3. You can set a global fallback in `config/test.exs`:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this was already set as #2 (and we're just changing it to #3), but now that we're adding another option, it makes this seem less preferred.

I still think the global config is the most common and simplest option. Since most apps aren't umbrella apps, I'd like to keep it as the "default" for people. So, I'd like to keep it as the first option. And only the people who can't use it like that can look at what other options to opt into.

So, could we set this as option 1? Then using the put_endpoint on the conn as second, and only mentioned the per-process Config one as third?

That makes sense in my head because it goes from basic to complex:

  1. Single Phoenix app - use global
  2. Umbrella app without JS - use put_endpoint for conn
  3. Umbrella app with JS and needs per-process stuff, use the new thing.


@process_key :phoenix_test_endpoint

def put_endpoint(endpoint) when is_atom(endpoint) do
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'll be confusing that we have a PhoenixTest.put_endpoint and a PhoenixTest.Config.put_endpoint. Rather than do that, I wonder if we create a new function and call it PhoenixTest.put_process_endpoint. Perhaps it's a little to explicit, but it's (hopefully) clear.

What do you think?

Comment on lines +18 to +34
No endpoint configured for PhoenixTest.

Set it per-test-process in your test setup (recommended for umbrella apps):

setup do
PhoenixTest.Config.put_endpoint(MyAppWeb.Endpoint)
:ok
end

Or set it on the conn directly:

{:ok, conn: Phoenix.ConnTest.build_conn() |> PhoenixTest.put_endpoint(MyAppWeb.Endpoint)}

Or set it globally in config/test.exs:

config :phoenix_test, :endpoint, MyAppWeb.Endpoint
"""
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also invert the order in which we show these messages? (see my other comment).

I'd prefer it to be like this since it goes from simple & common case to more complex and less common (at least in my mind):

  1. global
  2. build_conn |> put_endpoint
  3. process put_endpoint

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants