View Source Examples
Situations triggered by another process
Let's assume we have a unique resource and function that is fetching it if exist or create new otherwise.
@service Mockery.of("Service")
def fetch_or_create do
with \
{:error, :not_found} <- @service.fetch(),
{:ok, resource} <- @service.create()
do
{:ok, resource}
else
{:ok, resource} ->
{:ok, resource}
{:error, %Changeset{errors: [identifier: "has already been taken"]}} ->
fetch_or_create()
end
end
{:error, %Changeset{}}
in else clause can match only if between @service.fetch
and @service.create
there was another process that successfully created resource (Service.create()
)
How to cover {:error, %Changeset{}} -> fetch_or_create()
with tests?
mock Service, :create, {:error, %Ecto.Changeset{...}}
mock Service, [fetch: 0], fn ->
mock Service, [fetch: 0], &Service.fetch/0
{:error, :not_found}
end
assert {:ok, %Resource{}} = fetch_or_create()
In solution above:
Service.create/0
is permanently mocked to return{:error, %Changeset{}}
- First call of
Service.fetch/0
changes mock for next calls to original function and returns{:error, :not_found}
- Second call of
Service.fetch/0
returns{:ok, resource}
Task.Supervisor
Run tasks synchronously in test environment for easier testing
defmodule MyApp.Service do
def something, do: "value"
end
defmodule MyApp.Controller do
@task_supervisor Mockery.of("Task.Supervisor")
@service Mockery.of("MyApp.Service")
def action do
@task_supervisor.start_child(MyApp.TaskSupervisor, fn->
@service.something()
end)
end
end
defmodule MyApp.ControllerTest do
use ExUnit.Case, async: true
import Mockery.Assertions
test "something is called" do
mock Task.Supervisor, :start_child, fn(_, fun) -> fun.() end
MyApp.Controller.action()
assert_called MyApp.Service, :something
end
end