Current section
Files
Jump to
Current section
Files
lib/valid_field.ex
defmodule ValidField do
import ExUnit.Assertions, only: [assert: 2]
@moduledoc ~S"""
ValidField allows for unit testing values against a changeset.
"""
@doc """
Raises an ExUnit.AssertionError when the values for the field are invalid for
the changset provided. Returns the original changset map from `with_changeset/1`
to allow subsequent calls to be piped
## Examples
iex> ValidField.with_changeset(%Model{})
...> |> ValidField.assert_valid_field(:first_name, ["Test"])
...> |> ValidField.assert_valid_field(:last_name, ["Value"])
iex> ValidField.with_changeset(%Model{})
...> |> ValidField.assert_valid_field(:first_name, [nil, ""])
** (ExUnit.AssertionError) Expected the following values to be valid for "first_name": nil, ""
"""
@spec assert_valid_field(map, atom, list) :: map
def assert_valid_field(changeset, field, values) do
invalid_values = _map_value_assertions(changeset, field, values)
|> Enum.filter_map(fn {_key, value} -> value end, fn {key, _value} -> key end)
assert invalid_values == [], "Expected the following values to be valid for #{inspect Atom.to_string(field)}: #{_format_values invalid_values}"
changeset
end
@doc """
Raises an ExUnit.AssertionError when the values for the field are valid for
the changset provided. Returns the original changset map from `with_changeset/1`
to allow subsequent calls to be piped
## Examples
iex> ValidField.with_changeset(%Model{})
...> |> ValidField.assert_invalid_field(:first_name, [nil])
...> |> ValidField.assert_invalid_field(:first_name, [""])
iex> ValidField.with_changeset(%Model{})
...> |> ValidField.assert_invalid_field(:first_name, ["Test"])
** (ExUnit.AssertionError) Expected the following values to be invalid for "first_name": "Test"
"""
@spec assert_invalid_field(map, atom, list) :: map
def assert_invalid_field(changeset, field, values) do
valid_values = _map_value_assertions(changeset, field, values)
|> Enum.filter_map(fn {_key, value} -> !value end, fn {key, _value} -> key end)
assert valid_values == [], "Expected the following values to be invalid for #{inspect Atom.to_string(field)}: #{_format_values valid_values}"
changeset
end
@doc """
Returns a changeset map to be used with `assert_valid_field/3` or
`assert_invalid_field/3`. When with_changeset is passed a single arguments, it is
assumed to be an Ecto Model struct and will call the `changeset` function on
the struct's module
## Examples
ValidField.with_changeset(%Model{})
|> ValidField.assert_invalid_field(:first_name, [nil])
|> ValidField.assert_invalid_field(:first_name, [""])
"""
@spec with_changeset(Ecto.Model.t) :: map
def with_changeset(model), do: with_changeset(model, &model.__struct__.changeset/2)
@doc """
Returns a changeset map to be used with `assert_valid_field/3` or
`assert_invalid_field/3`. The function passed to `with_changeset/2` must accept two
arguments, the first being the model provided to `with_changeset/2`, the second
being the map of properties to be applied in the changeset.
## Examples
ValidField.with_changeset(%Model{}, &Model.changeset/2)
|> ValidField.assert_invalid_field(:first_name, [nil])
|> ValidField.assert_invalid_field(:first_name, [""])
"""
@spec with_changeset(Ecto.Model.t, function) :: map
def with_changeset(model, func) when is_function(func), do: %{model: model, changeset_func: func}
defp _format_values(values) do
values
|> Enum.map(&inspect/1)
|> Enum.join(", ")
end
defp _map_value_assertions(changeset, field, values) do
values
|> Enum.map(fn value -> {value, _is_invalid_for(changeset, field, value)} end)
end
defp _is_invalid_for(%{model: model, changeset_func: changeset}, field, value) do
params = Map.put(%{},field, value)
changeset.(model, params).errors
|> Dict.has_key?(field)
end
end