Skip to content

Instantly share code, notes, and snippets.

@nyaray
Last active June 6, 2019 10:00
Show Gist options
  • Save nyaray/145749564603281e860a735ccd47bb26 to your computer and use it in GitHub Desktop.
Save nyaray/145749564603281e860a735ccd47bb26 to your computer and use it in GitHub Desktop.
Specification checking thing for verifying interactions with mocks.
defmodule SpecCheck do
defmodule MockInteractionException do
@moduledoc """
A structured representation of one of:
- unmet expectation
- missing interactions
- unexpected interactions
"""
defexception message: "Interaction with mock did not match expectation.",
description: nil,
issue: nil,
line: 0
def unmet(reason, line) do
%MockInteractionException{
description: "Wrong interaction",
issue: {:unmet, {line, reason}}
}
end
def expected(spec, line) do
%MockInteractionException{
description: "Missing interactions",
issue: {:expected, spec},
line: line
}
end
def unexpected(actual, line) do
%MockInteractionException{
description: "Unexpected interactions",
issue: {:unexpected, actual},
line: line
}
end
end
@doc """
check/2-3 will report when `actual` does not conform to `spec` when given
- `spec`, a poor man's match spec, and
- `actual`, an arbitrary term
Both `spec` and `actual` are expected to be lists
NOTE: lines defaults to `1` because the interactions are numbered for humans.
"""
def check(spec, actual, lines \\ 1) do
case {spec, actual} do
{[], []} ->
{:ok, lines}
{spec, []} ->
throw(MockInteractionException.expected(spec, lines))
{[], actual} ->
throw(MockInteractionException.unexpected(actual, lines))
{[s | spec_rest], [a | actual_rest]} ->
case check_step(s, a) do
:cont -> check(spec_rest, actual_rest, lines + 1)
{:halt, reason} -> throw(MockInteractionException.unmet(reason, lines))
end
end
end
#
# pairwise element check of the respective heads of spec and actual
#
# check "don't care"
defp check_step(:_, _), do: :cont
# check bitstrings
defp check_step(:bitstring, b) when is_bitstring(b), do: :cont
defp check_step(:bitstring, bad), do: mismatch(:bitstring, bad)
# check keys in tuples
defp check_step({:_, sv}, {_ak, av}), do: check_step(sv, av)
defp check_step({:bitstring, sv}, {b, av}) when is_bitstring(b), do: check_step(sv, av)
defp check_step({spec_key, sv}, {spec_key, av}), do: check_step(sv, av)
defp check_step({spec_key, _sv}, {bad_key, _av}), do: mismatch(spec_key, bad_key)
# check exact match, last resort
defp check_step(term, term), do: :cont
defp check_step(term, bad), do: mismatch(term, bad)
# utility
defp mismatch(spec, actual), do: {:halt, %{expected: spec, actual: actual}}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment