Current section
Files
Jump to
Current section
Files
lib/stub_alias.ex
defmodule StubAlias do
@moduledoc """
Replaces `alias MyModule` calls with a different module based upon environment configuration. Instead of using
mocks to mock out undesired function calls or side effects, use `stub_alias/2` to replace that call to a different
module without mocking. `StubAlias` replaces your `alias SideEffectModule` calls at test time with `alias NonSideEffectModule`
or a module of your choice based upon configuration.
This is useful for external APIs in which HTTP calls are necessary, or database interactions that are undesired. To use
add an import to `StubAlias` and then replace your `alias` calls with `stub_alias`. Then in the `:test` environment (or
wherever) add configuration mapping to the new module intended to use at test time.
Example:
defmodule UserService do
import StubAlias
stub_alias HTTPoison
def find(id) do
HTTPoison.get!("http://www.api.com/v1/users/\#{id}") |> handle_response()
end
# Private method so can't be tested directly
defp handle_response(%HTTPoison.Response{status_code: 200, body: body}) do
Poison.decode!(body) # Handle serialization logic, ect
end
defp handle_response(%HTTPoison.Response{status_code: code}) when code != 200 do
:error
end
end
Given the following configuration for the test environment:
config :stub_alias,
"HTTPoison": MyApp.Stubs.HTTPoison
And then in `test/support/stubs/httpoison.ex` a simple file like:
defmodule MyApp.Stubs.HTTPoison do
# Test time call goes here instead of HTTPoison and no request is made!
def get!("http://www.api.com/v1/users/1") do
%{users: []} # Data we want to test against, could easily be an agent to allow the test to set the data
|> Poison.encode!
end
end
"""
@doc """
Use to replace `alias` calls with environment specific modules. See `StubAlias` for more information.
"""
defmacro stub_alias(aliases, opts \\ []) do
_stub_alias(aliases, opts)
end
defp _stub_alias({:__aliases__, _, module}, opts) do
concatted_module = module |> Enum.map(&to_string/1) |> Enum.join(".") |> String.to_atom
quote do
alias unquote(Module.concat(:Elixir, Application.get_env(:stub_alias, concatted_module, concatted_module))), unquote(opts)
end
end
defp _stub_alias({{:., [], [{:__aliases__, _, base_modules}, :{}]}, [], aliases}, opts) do
{:__block__, [],
Enum.map(aliases, fn({:__aliases__, [alias: false], module}) ->
_stub_alias({:__aliases__, [alias: false], base_modules ++ module}, opts)
end)
}
end
if Mix.env == :test do
defp _stub_alias(unknown, opts) do
raise "Received unhandled code: #{inspect unknown} with opts #{inspect opts}"
end
end
end