defmodule Pavlov.Matchers do @moduledoc """ Provides several matcher functions. Matchers accept up to two values, `actual` and `expected`, and return a Boolean. Using "Expects" syntax, all matchers have positive and negative forms. For a matcher `eq`, there is a positive `to_eq` and a negative `not_to_eq` method. """ import ExUnit.Assertions, only: [flunk: 1] @type t :: list | map @doc """ Performs an equality test between two values using ==. Example: eq(1, 2) # => false eq("a", "a") # => true """ @spec eq(any, any) :: boolean def eq(actual, expected) do actual == expected end @doc """ Performs an equality test between a given expression and 'true'. Example: be_true(1==1) # => true be_true("a"=="b") # => false """ @spec be_true(any) :: boolean def be_true(exp) do exp == true end @doc """ Performs a truthy check with a given expression. Example: be_truthy(1) # => true be_truthy("a") # => true be_truthy(nil) # => false be_truthy(false) # => false """ @spec be_truthy(any) :: boolean def be_truthy(exp) do exp end @doc """ Performs a falsey check with a given expression. Example: be_falsey(1) # => false be_falsey("a") # => false be_falsey(nil) # => true be_falsey(false) # => true """ @spec be_falsey(any) :: boolean def be_falsey(exp) do !exp end @doc """ Performs a nil check with a given expression. Example: be_nil(nil) # => true be_nil("a") # => false """ @spec be_nil(any) :: boolean def be_nil(exp) do is_nil exp end @doc """ Performs has_key? operation on a Dict. Example: have_key(%{:a => 1}, :a) # => true have_key(%{:a => 1}, :b) # => false """ @spec have_key(node, t) :: boolean def have_key(key, dict) do Dict.has_key? dict, key end @doc """ Checks if a Dict is empty. Example: be_empty(%{}) # => true be_empty(%{:a => 1}) # => false """ @spec be_empty(t|char_list) :: boolean def be_empty(list) do cond do is_bitstring(list) -> String.length(list) == 0 is_list(list) || is_map(list) -> Enum.empty? list true -> false end end @doc """ Tests whether a given value is part of an array. Example: include([1, 2, 3], 1) # => true include([1], 2) # => false """ @spec include(any, list|char_list) :: boolean def include(member, list) do cond do is_bitstring(list) -> String.contains? list, member is_list(list) || is_map(list) -> Enum.member? list, member true -> false end end @doc """ Tests whether a given exception was raised. Example: have_raised(fn -> 1 + "test") end, ArithmeticError) # => true have_raised(fn -> 1 + 2) end, ArithmeticError) # => false """ @spec have_raised(any, function) :: boolean def have_raised(exception, fun) do raised = try do fun.() rescue error -> stacktrace = System.stacktrace name = error.__struct__ cond do name == exception -> error name == ExUnit.AssertionError -> reraise(error, stacktrace) true -> flunk "Expected exception #{inspect exception} but got #{inspect name} (#{Exception.message(error)})" end else _ -> false end end @doc """ Tests whether a given value was thrown. Example: have_thrown(fn -> throw "x" end, "x") # => true have_thrown(fn -> throw "x" end, "y") # => false """ @spec have_thrown(any, function) :: boolean def have_thrown(expected, fun) do value = try do fun.() catch x -> x end value == expected end @doc """ Tests whether the process has exited. Example: have_exited(fn -> exit "x" end) # => true have_thrown(fn -> :ok end) # => false """ @spec have_exited(function) :: boolean def have_exited(fun) do exited = try do fun.() catch :exit, _ -> true end case exited do true -> true _ -> false end end end