Packages

The Copeiro package provides assertion functions that will enhance your testing experience in Elixir

Current section

Files

Jump to
copeiro lib copeiro.ex
Raw

lib/copeiro.ex

defmodule Copeiro do
@moduledoc """
The Copeiro package provides assertion functions that will enhance your testing experience in Elixir
"""
@doc """
Asserts that two lists matches
## Examples
For the following examples `left` and `right` will be used to describe the expression `assert_lists left OPERATOR right`
### All elements of `left` are also elements of `right`
```
iex> assert_lists [1, 2] in [0, 2, 1, 3]
true
iex> assert_lists [{:a, 1}, {:c, 3}] in [{:a, 1}, {:b, 2}, {:c, 3}]
true
```
### `left` and `right` has no element in common
```
iex> assert_lists [1, 2] not in [3, 4]
true
iex> assert_lists [%{c: 3}, %{d: 4}] not in [%{a: 1}, %{b: 2}]
true
```
### Asserts that two lists match in any order
```
iex> assert_lists [1, 2, 3] == [2, 1, 3], any_order: true
true
iex> assert_lists [{:a, 0}, {:b, 1}, {:c, 3}] == [{:a, 0}, {:c, 3}, {:b, 1}], any_order: true
true
```
### Asserting lists of maps/structs
```
iex> assert_lists [%{a: 1}, %{a: 2}] in [%{a: 1, b: 1}, %{a: 2, b: 2}, %{a: 3, b: 3}], keys: [:a]
true
iex> assert_lists [%{a: 1}, %{a: 2}] == [%{a: 2, b: 2}, %{a: 1, b: 1}], keys: [:a], any_order: true
true
```
"""
defmacro assert_lists(expr, opts \\ []) do
{op, left, right} =
case expr do
{:not, _, [{:in, _, [left, right]}]} -> {:not_in, left, right}
{op, _, [left, right]} -> {op, left, right}
end
quote bind_quoted: [op: op, left: left, right: right, opts: opts], location: :keep do
{left, right, opts} = Copeiro.__access_keys__(left, right, opts)
Copeiro.__assert_lists__(op, left, right, opts)
end
end
@doc false
def __assert_lists__(:==, left, right, any_order: true) do
left
|> Copeiro.Comparator.match_lists_in_any_order(right)
|> case do
{:error, {direction, value}} ->
ExUnit.Assertions.flunk("""
assertion failed, lists does not match
value-#{direction}: #{inspect(value)}
left: #{inspect(left)}
right: #{inspect(right)}
""")
:ok ->
true
end
end
def __assert_lists__(:==, left, right, _opts) do
if left == right do
true
else
ExUnit.Assertions.flunk("""
Comparison (using ==) failed in:
left: #{inspect(left)}
right: #{inspect(right)}
""")
end
end
def __assert_lists__(:in, left, right, _opts) do
left
|> Enum.reduce_while(:ok, fn l, _ ->
if l in right do
{:cont, :ok}
else
{:halt, {:error, l}}
end
end)
|> case do
{:error, value} ->
ExUnit.Assertions.flunk("""
assertion failed, value not found
value: #{inspect(value)}
left: #{inspect(left)}
right: #{inspect(right)}
""")
:ok ->
true
end
end
def __assert_lists__(:not_in, left, right, _opts) do
left
|> Enum.reduce_while(true, fn l, _ ->
case l not in right do
true -> {:cont, true}
false -> {:halt, {:error, l}}
end
end)
|> case do
{:error, value} ->
ExUnit.Assertions.flunk("""
match succeeded, but should have failed
value: #{inspect(value)}
left: #{inspect(left)}
right: #{inspect(right)}
""")
_ ->
true
end
end
@doc false
def __access_keys__(left, right, opts) do
{keys, opts} = Keyword.pop(opts, :keys, [])
if keys == [] do
{left, right, opts}
else
{do_access_keys(left, keys), do_access_keys(right, keys), opts}
end
end
defp do_access_keys(lst, keys), do: Enum.map(lst, &Map.take(&1, keys))
end