Current section
Files
Jump to
Current section
Files
lib/rewire.ex
defmodule Rewire do
@moduledoc """
Rewire is a libary for replacing hard-wired dependencies of the module your unit testing.
This keeps your production code free from any unit testing-specific concerns.
## Usage
Given a module such as this:
```elixir
# this module has a hard-wired dependency on the `English` module
defmodule Conversation do
@punctuation "!"
def start(), do: English.greet() <> @punctuation
end
```
If you define a `mox` mock `EnglishMock` you can rewire the dependency in your unit test:
```elixir
defmodule MyTest do
use ExUnit.Case, async: true
import Rewire # (1) activate `rewire`
import Mox
rewire Conversation, English: EnglishMock # (2) rewire `English` to `EnglishMock`
test "start/0" do
stub(EnglishMock, :greet, fn -> "g'day" end)
assert Conversation.start() == "g'day!" # (3) test using the mock
end
end
```
This example uses `mox`, but `rewire` is mocking library-agnostic.
You can use multiple `rewire`s and multiple overrides:
```elixir
rewire Conversation, English: EnglishMock
rewire OnlineConversation, Email: EmailMock, Chat: ChatMock
```
You can also give the alias a different name using `as`:
```elixir
rewire Conversation, English: EnglishMock, as: SmallTalk
```
Alternatively, you can also rewire a module inside a block:
```elixir
rewire Conversation, English: EnglishMock do # (1) only rewired inside the block
stub(EnglishMock, :greet, fn -> "g'day" end)
assert Conversation.start() == "g'day!" # (2) test using the mock
end
```
Plus, you can also rewire module attributes.
"""
import Rewire.Utils
# left for backwards-compability
defmacro __using__(_) do
quote do
# Needed for importing the `rewire` macro.
import Rewire
end
end
@doc false
defmacro rewire({:__aliases__, _, _}),
do: invalid_rewire("options are missing", __CALLER__)
@doc false
defmacro rewire(_),
do: invalid_rewire("the first argument must be an Elixir module", __CALLER__)
@doc false
defmacro rewire({:__aliases__, _, _}, do: _block),
do: invalid_rewire("options are missing", __CALLER__)
@doc """
Macro that allows to rewire (and alias) a module.
```elixir
import Rewire
rewire App.ModuleToRewire, ModuleDep: Mock
# `ModuleToRewire` will use `Mock` now
end
```
## Options
`opts` is a keyword list:
* `as` - give the rewired module a different name
* any other item, like `ModuleDep: Mock`, will be interpreted as a mapping from one module to another
"""
defmacro rewire({:__aliases__, _, rewire_module_ast}, opts) do
opts = parse_opts(rewire_module_ast, opts, __CALLER__)
Rewire.Alias.rewire_alias(opts)
end
@doc """
Macro that allows to rewire a module within a block.
```elixir
import Rewire
rewire App.ModuleToRewire, ModuleDep: Mock do
# `ModuleToRewire` will use `Mock` now
end
```
See `rewire/2` for a description of options.
"""
defmacro rewire({:__aliases__, _, rewire_module_ast}, opts, do: block) do
opts = parse_opts(rewire_module_ast, opts, __CALLER__)
Rewire.Block.rewire_block(opts, block)
end
@doc false
defmacro rewire(_, _opts, do: _block),
do: invalid_rewire("the first argument must be an Elixir module", __CALLER__)
defp invalid_rewire(reason, %{file: file, line: line}),
do:
raise(CompileError,
description: "unable to rewire: #{reason}",
file: file,
line: line
)
end