Current section
Files
Jump to
Current section
Files
lib/moxinet.ex
defmodule Moxinet do
@moduledoc """
`Moxinet` helps you mock the internet at the HTTP layer
without sacrificing parallel testing.
"""
@doc """
The start functions which will start the `Moxinet` server.
You'd most likely want to put run this function from
your `test_helper.exs`:
```elixir
{:ok, _pid} = Moxinet.start(router: MyMockServer, port: 4010)
```
## Options
- `router`: A reference to your mock server. *Required*
- `port`: The port your mock server will run on. *Required*
- `name`: Name of the moxinet supervisor. Defaults to `Moxinet`
- `signature_storage`: Name of the signature storage server. Defaults to `Moxinet.SignatureStorage`
"""
@spec start(Keyword.t()) :: {:ok, pid} | {:error, atom()}
defdelegate start(opts), to: Moxinet.Application
@doc """
Returns the header needed to be included in requests to the
mock servers in order to support parallel runs.
"""
@spec build_mock_header(pid()) :: {String.t(), String.t()}
def build_mock_header(pid \\ self()) when is_pid(pid) do
{"x-moxinet-ref", pid_reference(pid)}
end
@doc """
Turns a pid into a reference which could be used for indexing
the signatures.
"""
@spec pid_reference(pid()) :: String.t()
def pid_reference(pid) when is_pid(pid) do
callers = Process.get(:"$callers") || [pid]
case NimbleOwnership.fetch_owner(Moxinet.SignatureStorage, callers, :mocks) do
{:ok, owner_pid} ->
Moxinet.PidReference.encode(owner_pid)
{:shared_owner, shared_owner_pid} ->
Moxinet.PidReference.encode(shared_owner_pid)
:error ->
Moxinet.PidReference.encode(pid)
end
end
@doc """
Mocks a call for the passed module by defining a signature based on the pid, http method and path.
## Options:
* `pid`: The source pid that the mock will be applied for. Defaults to `self()`
* `times`: The amount of times the mock signature may be used. Defaults to `1`
* `storage`: The signature storage to be used. Defaults to `Moxinet.SignatureStorage`
## Examples:
Moxinet.expect(MyMock, :get, "/path/to/resource", fn _body ->
%Moxinet.Response{status: 200, body: "My response body"}
end)
"""
@type http_method :: :get | :post | :patch | :put | :delete | :options
@spec expect(
module(),
http_method,
binary(),
Moxinet.SignatureStorage.Mock.callback(),
Moxinet.SignatureStorage.store_options()
) ::
:ok
def expect(module, http_method, path, callback, options \\ []) do
test_pid = self()
storage = Keyword.get(options, :storage, Moxinet.SignatureStorage)
:ok = setup_exunit_callback(test_pid, storage)
Moxinet.SignatureStorage.store(module, http_method, path, callback, options)
end
defp setup_exunit_callback(test_pid, storage) do
ExUnit.Callbacks.on_exit({Moxinet, self()}, fn ->
verify_usage!(test_pid, storage)
end)
end
@doc """
Allows `pid_to_allow` to use mocks defined by `pid_with_access`.
This is useful when a test spawns processes that need access to the tests
expectations. By default, most mocks set up with `expect/5` will work out of the box
but in scenarios where the allowance cannot be inferred through `$callers`,
it must be explicitly defined.
## Example
A common use case is when the code under test spawns a process that makes HTTP requests:
test "spawned process can use parent mocks" do
parent = self()
Moxinet.expect(MyMock, :get, "/users", fn _body ->
%Moxinet.Response{status: 200, body: ~s({"id": 1})}
end)
task =
spawn(fn ->
Moxinet.allow(parent, self())
# Now this process can make requests that use the mock
{:ok, response} = MyHTTPClient.get("/users")
response
send(parent, response)
end)
assert_receive %{id: 1}
end
"""
@spec allow(pid(), pid() | (-> pid())) :: :ok | {:error, NimbleOwnership.Error.t()}
def allow(pid_with_access, pid_to_allow)
when is_pid(pid_with_access) and (is_pid(pid_to_allow) or is_function(pid_to_allow, 0)) do
NimbleOwnership.allow(Moxinet.SignatureStorage, pid_with_access, pid_to_allow, :mocks)
end
@doc """
Verifies that all defined expectations have been called to prevent tests from
defining expectations that aren't used. Recommended usage is to set it up in
your test setup:
```elixir
setup :verify_usage!
```
"""
@spec verify_usage!(pid()) :: :ok | no_return()
defdelegate verify_usage!(test_pid, storage_pid \\ Moxinet.SignatureStorage),
to: Moxinet.SignatureStorage
end