-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathrewire.ex
138 lines (104 loc) · 3.51 KB
/
rewire.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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