View Source Environment Variable Example
This example demonstrates how ProcessTree
can be used in tests to provide custom values for
environment variables, while preserving async: true
.
The example is a Phoenix app with a single LiveView that serves the site's index page. The
LiveView uses an environment variable called cutoff_date
to customize the appearance of the
index page. When a viewer hits the page before the cutoff date, they see this message:
Welcome! You've made the cutoff date :-)
When a viewer hits the page after the cutoff date, they see this message:
Sorry! You've missed the cutoff date :-(
For "real-life" usage when MIX_ENV=dev
or MIX_ENV=prod
, the cutoff_date
is set in runtime.exs
as a hardcoded
Application environment variable:
config :environment_variable_example, cutoff_date: ~D[2024-01-01]
In our tests of the LiveView, we override the value on a test-specific basis by writing the desired custom value to the process dictionary of the ExUnit test pid:
test "when the cutoff date has not passed, shows a 'welcome' message", %{conn: conn} do
tomorrow = Date.add(Date.utc_today(), 1)
# set the cutoff date to tomorrow by writing the value
# to the process dictionary of the ExUnit test pid
Process.put(:cutoff_date, tomorrow)
{:ok, _show_live, html} = live(conn, ~p"/welcome")
assert html =~ "Welcome!"
end
test "when cutoff date has passed, shows a 'sorry' message", %{conn: conn} do
yesterday = Date.add(Date.utc_today(), -1)
# set the cutoff date to yesterday by writing the value
# to the process dictionary of the ExUnit test pid
Process.put(:cutoff_date, yesterday)
{:ok, _show_live, html} = live(conn, ~p"/welcome")
assert html =~ "Sorry!"
end
Since this custom value is scoped to individual ExUnit tests, rather than to the global Application environment, the tests are safe for async: true
.
The code for the LiveView is below. In the cutoff_date()
function, the LiveView uses ProcessTree
to look for a customized value. If no custom value is found (as when running in dev
or prod
), then the LiveView uses the cutoff_date
retrieved from the Application environment, as configured in runtime.exs
defmodule EnvironmentVariableExampleWeb.WelcomeLive.Show do
@moduledoc false
use EnvironmentVariableExampleWeb, :live_view
@impl true
def render(assigns) do
message = case Date.before?(Date.utc_today(), cutoff_date()) do
true ->
"Welcome! You've made the cutoff date :-)"
false ->
"Sorry! You've missed the cutoff date :-("
end
assigns = assign(assigns, :message, message)
~H"""
<h1 style='padding: 3em'><%= @message %></h1>
"""
end
defp cutoff_date() do
# read the default cutoff_date from the Application environment
app_env_cutoff_date = Application.get_env(:environment_variable_example, :cutoff_date)
# ProcessTree will look for a customized cutoff_date value in the process dictionaries
# of this process and its ancestors.
#
# When running our tests, ProcessTree will find and return the customized value that
# we have inserted into the process dictionary of the ExUnit test pid, which is an
# ancestor of this LiveView process.
#
# When running in production, ProcessTree will not find a value in any ancestor
# dictionaries, and it will return the default value after caching it in the process
# dictionary of the current process.
ProcessTree.get(:cutoff_date, default: app_env_cutoff_date)
end
end
During tests, the LiveView "sees" the custom value because ProcessTree
looks up the process ancestry hierarchy to
find the value. The LiveView process is spawned by the ExUnit test pid (indirectly, via the ExUnit test supervisor), meaning that the test pid is an ancestor of the LiveView. ProcessTree
eventually finds the custom value in the process dictionary of the test pid.
In production, ProcessTree
returns the default value obtained from the Application environment.