diff --git a/README.md b/README.md index aa2cd07..b381705 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,15 @@ Parameter types and return types are not checked. Hammox now includes telemetry events! See [Telemetry Guide](https://hexdocs.pm/hammox/Telemetry.html) for more information. +## Configuration + +Hammox includes experimental pretty printing of error messages. +To enable it add to `config/test.exs` + +```elixir +config :hammox, pretty: true +``` + ## License Copyright 2019 MichaƂ Szewczak diff --git a/lib/hammox/type_match_error.ex b/lib/hammox/type_match_error.ex index 785235b..13d84b4 100644 --- a/lib/hammox/type_match_error.ex +++ b/lib/hammox/type_match_error.ex @@ -14,6 +14,22 @@ defmodule Hammox.TypeMatchError do } end + defp message_string(reasons) when is_list(reasons) do + reasons + |> Enum.with_index() + |> Enum.map_join("\n\n", fn {reason, index} -> + padding = put_padding(index) + + reason + |> human_reason() + |> String.replace_prefix("", padding) + end) + end + + defp message_string(reason) when is_tuple(reason) do + message_string([reason]) + end + defp human_reason({:arg_type_mismatch, index, value, type}) do "#{Ordinal.ordinalize(index + 1)} argument value #{custom_inspect(value)} does not match #{Ordinal.ordinalize(index + 1)} parameter's type #{type_to_string(type)}." end @@ -95,22 +111,6 @@ defmodule Hammox.TypeMatchError do "Value #{custom_inspect(value)} does not implement the #{protocol_name} protocol." end - defp message_string(reasons) when is_list(reasons) do - reasons - |> Enum.with_index() - |> Enum.map_join("\n\n", fn {reason, index} -> - padding = put_padding(index) - - reason - |> human_reason() - |> String.replace_prefix("", padding) - end) - end - - defp message_string(reason) when is_tuple(reason) do - message_string([reason]) - end - defp type_to_string({:type, _, :map_field_exact, [type1, type2]}) do "required(#{type_to_string(type1)}) => #{type_to_string(type2)}" end @@ -135,19 +135,27 @@ defmodule Hammox.TypeMatchError do end defp format_multiple(type_string) do - padding = "\n" <> get_padding() + if pretty_print() do + padding = "\n" <> get_padding() - type_string - |> String.replace(" | ", padding <> " | ") - |> String.replace_prefix("", padding) + type_string + |> String.replace(" | ", padding <> " | ") + |> String.replace_prefix("", padding) + else + type_string + end end defp custom_inspect(value) do - padding = get_padding() - - value - |> inspect(limit: :infinity, printable_limit: 500, pretty: true) - |> String.replace("\n", "\n" <> padding) + if pretty_print() do + padding = get_padding() + + value + |> inspect(limit: :infinity, printable_limit: 500, pretty: true) + |> String.replace("\n", "\n" <> padding) + else + inspect(value) + end end defp put_padding(level) when is_integer(level) do @@ -165,4 +173,8 @@ defmodule Hammox.TypeMatchError do defp get_padding() do Process.get(:padding) end + + defp pretty_print do + Application.get_env(:hammox, :pretty) + end end diff --git a/test/hammox/type_match_error_test.exs b/test/hammox/type_match_error_test.exs new file mode 100644 index 0000000..0c386dd --- /dev/null +++ b/test/hammox/type_match_error_test.exs @@ -0,0 +1,96 @@ +defmodule Hammox.TypeMatchErrorTest do + use ExUnit.Case, async: true + + alias Hammox.TypeMatchError + + describe "standard error" do + test "reason" do + error = TypeMatchError.exception({:error, reason()}) + + assert error.message == + """ + + Returned value {:ok, %{__struct__: Post, body: "post body", post_body: "nil"}} does not match type {:ok, Post.t()} | {:error, any()}. + + Value {:ok, %{__struct__: Post, body: "post body", post_body: "nil"}} does not match type {:ok, Post.t()} | {:error, any()}. + + 1st tuple element :ok does not match 1st element type :error. + + Value :ok does not match type :error. + """ + |> String.replace_trailing("\n", "") + end + end + + describe "pretty error" do + setup do + Application.put_env(:hammox, :pretty, true) + + on_exit(fn -> + Application.delete_env(:hammox, :pretty) + end) + end + + test "reason" do + error = TypeMatchError.exception({:error, reason()}) + + assert error.message == + """ + + Returned value {:ok, %{__struct__: Post, body: \"post body\", post_body: \"nil\"}} does not match type + {:ok, Post.t()} + | {:error, any()}. + + Value {:ok, %{__struct__: Post, body: \"post body\", post_body: \"nil\"}} does not match type + {:ok, Post.t()} + | {:error, any()}. + + 1st tuple element :ok does not match 1st element type + :error. + + Value :ok does not match type + :error. + """ + |> String.replace_trailing("\n", "") + end + end + + defp reason do + [ + {:return_type_mismatch, + {:ok, + %{ + __struct__: Post, + body: "post body", + post_body: "nil" + }}, + {:type, 49, :union, + [ + {:type, 0, :tuple, + [ + {:atom, 0, :ok}, + {:remote_type, 49, [{:atom, 0, Post}, {:atom, 0, :t}, []]} + ]}, + {:type, 0, :tuple, [{:atom, 0, :error}, {:type, 49, :any, []}]} + ]}}, + {:type_mismatch, + {:ok, + %{ + __struct__: Post, + body: "post body", + post_body: "nil" + }}, + {:type, 49, :union, + [ + {:type, 0, :tuple, + [ + {:atom, 0, :ok}, + {:remote_type, 49, [{:atom, 0, Post}, {:atom, 0, :t}, []]} + ]}, + {:type, 0, :tuple, [{:atom, 0, :error}, {:type, 49, :any, []}]} + ]}}, + {:tuple_elem_type_mismatch, 0, :ok, {:atom, 0, :error}}, + {:type_mismatch, :ok, {:atom, 0, :error}} + ] + end +end diff --git a/test/hammox_test.exs b/test/hammox_test.exs index 7d93a87..e780fca 100644 --- a/test/hammox_test.exs +++ b/test/hammox_test.exs @@ -1198,7 +1198,7 @@ defmodule HammoxTest do assert_raise( Hammox.TypeMatchError, - ~r/1st argument value "bar" does not match 1st parameter's type \natom()./, + ~r/1st argument value "bar" does not match 1st parameter's type atom()./, fn -> TestMock.foo_unnamed_arg("bar") end ) end diff --git a/test/support/behaviour.ex b/test/support/behaviour.ex index e95dbbd..5b32fba 100644 --- a/test/support/behaviour.ex +++ b/test/support/behaviour.ex @@ -26,7 +26,7 @@ defmodule Hammox.Test.Behaviour do @callback foo_bitstring_size_literal() :: <<_::3>> @callback foo_bitstring_unit_literal() :: <<_::_*3>> @callback foo_bitstring_size_unit_literal() :: <<_::2, _::_*3>> - @callback foo_nullary_function_literal() :: (-> :ok) + @callback foo_nullary_function_literal() :: (() -> :ok) @callback foo_binary_function_literal() :: (:a, :b -> :ok) @callback foo_any_arity_function_literal() :: (... -> :ok) @callback foo_integer_literal() :: 1