View Source SandboxRegistry (sandbox_registry v0.1.1)
SandboxRegistry
We can use the sandbox registry to help create sandboxes for testing
Sandboxes help us test by allow us to specify a mock that will be utilzed only for the specific test, allowing us to modify the return value of a specific function only in test.
We can utilize this pattern by building around an adapter pattern, and using the sandbox in dev mode. Other ways to build this pattern
include using a flag like sandbox?
to enable sandbox mode and swap out calls to a sanbox
example-sandbox
Example Sandbox
defmodule HTTPSandbox do
@registry :http_sandbox
@keys :unique
def start_link do
Registry.start_link(keys: @keys, name: @registry)
end
def set_get_responses(tuples) do
tuples
|> Map.new(fn {url, func} -> {{:get, url}, func} end)
|> then(&SandboxRegistry.register(@registry, @state, &1, @keys))
|> case do
:ok -> :ok
{:error, :registry_not_started} -> raise_not_started!()
end
# Random sleep is needed to allow registry time to insert
Process.sleep(50)
end
def get_response(url, headers, options) do
func = find!(:get, url)
func.(url, headers, options)
end
def find!(method, url) do
case SandboxRegistry.lookup(@registry, @state) do
{:ok, funcs} ->
find_response!(funcs, method, url)
{:error, :pid_not_registered} ->
raise """
No functions registered for #{inspect(self())}
Action: #{inspect(action)}
URL: #{inspect(url)}
======= Use: =======
#{format_example(action, url)}
=== in your test ===
"""
{:error, :registry_not_started} ->
raise """
Registry not started for #{inspect(__MODULE__)}.
Please add the line:
#{inspect(__MODULE__)}.start_link()
to test_helper.exs for the current app.
"""
end
end
end
Now we can use this in an HTTP module only in test by doing
defmodule HTTP do
@defaults_opts [
sandbox?: Mix.env() === :test
]
def get(url, header, opts) do
opts = Keyword.merge(@defaults_opts, opts)
if opts[:sandbox?] do
HTTPSandbox.get_response(url, headers, opts)
else
make_get_request()
end
end
end
Now in test we have the ability to mock out our get requests per test
describe "some get request" do
test "test get /url" do
HTTPSandbox.set_get_responses([{
"myurl.com",
fn _url, _headers, _opts -> {:ok, :whatever} end
}])
assert {:ok, :whatever} === HTTP.get("myurl.com", [], [])
end
end
installation
Installation
Available in Hex, the package can be installed
by adding sandbox_registry
to your list of dependencies in mix.exs
:
def deps do
[
{:sandbox_registry, "~> 0.1.0"}
]
end
The docs can be found at https://hexdocs.pm/sandbox_registry.
Registers a map of state per test process, allowing us to build mock functionality on top
usage
Usage
- Start the registry in your
test_helper.exs
Registry.start_link(keys: :duplicate, name: :some_registry)
:keys
can be :duplicate
or :unique
(duplicate is faster, but you will overwrite state if setting it more than once)
And the name/keys variables will need to be used in the following functions.
- Set the state in a setup block or within the test itself
SandboxRegistry.register(:some_registry, :my_context, %{key1: "value", key2: "other_value}, :duplicate)
- Access the state from anywhere in the application. SandboxRegistry functions are not available outside of test.
if Mix.env() === :test do
def get_cache_value(key)
:some_registry
|> SandboxRegistry.lookup(:my_context)
|> Map.fetch!(key)
else
defdelegate get_cache_value(key), to: RealProductionCache
end
Link to this section Summary
Functions
Get state for a pid or any of its ancestors
Get state for a specific pid
List all pids that have registered state for context
Registers a map of state for a test process.
Link to this section Types
Link to this section Functions
Get state for a pid or any of its ancestors
Get state for a specific pid
List all pids that have registered state for context
Registers a map of state for a test process.
keys are either :duplicate or :unique and must match the value that the registry was started with, i.e.
Registry.start_link(keys: :duplicate, name: :some_registry)
The with statement handles a couple of cases: 1.) If the Pid is already registered, update the map 2.) If update_value/3 fails with error, that means that the wrong process is attempting to update. This may be because a test case with the same PID was just killed, the registry hadn't been updated yet, and the current process with the same pid (because recycling is good for the earth) tried to access that stale entry. So retry!