Skip to content

Instantly share code, notes, and snippets.

@TylerPachal
Last active July 28, 2020 21:43
Show Gist options
  • Save TylerPachal/dcc2632edb7b8525052daee69d9cec45 to your computer and use it in GitHub Desktop.
Save TylerPachal/dcc2632edb7b8525052daee69d9cec45 to your computer and use it in GitHub Desktop.
defmodule EctoValidationsTest do
@moduledoc """
I set up these tests to document how some of the built-in Ecto Changeset
validations work. It seems as though:
- If a field is marked as required, no subsequent validation will be done
when the field is not present in the params.
- Duplicating validate_required checks does not add duplicate errors, but
duplicating other types of validations does add duplicate errors.
- If a field passes its validate_required check, then all subsequent
validation checks will be run, even if some fail (no early exiting).
- If changes are applying applied to an existing struct, the existing
values are only used for satisfying the validate_required check. If
there is invalid data already in the struct, none of the validation
checks will complain and the invalid data will persist.
"""
use ExUnit.Case
alias Ecto.Changeset
defmodule Person do
defstruct [:name, :age]
end
@types %{name: :string, age: :integer}
describe "on empty struct" do
test "when a field is missing, no further validation occurs on that field" do
params = %{"name" => "tyler"}
changeset =
{%Person{}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required([:name, :age])
|> Changeset.validate_inclusion(:age, 0..100) # This will not be run for this case
|> Changeset.validate_inclusion(:age, 13..19) # This will not be run for this case
assert changeset.valid? == false
assert changeset.errors == [
{:age, {"can't be blank", [validation: :required]}}
]
end
test "when a field is present, the next validations are all run, even if a previous one fails" do
params = %{"name" => "tyler", "age" => -1}
changeset =
{%Person{}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required([:name, :age])
|> Changeset.validate_inclusion(:age, 0..100)
|> Changeset.validate_exclusion(:age, [-1])
assert changeset.valid? == false
assert changeset.errors == [
{:age, {"is reserved", [{:validation, :exclusion}, {:enum, [-1]}]}},
{:age, {"is invalid", [{:validation, :inclusion}, {:enum, 0..100}]}}
]
end
test "duplicate validate_required checks do not add duplicate errors" do
params = %{"name" => "tyler"}
changeset =
{%Person{}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required(:age)
|> Changeset.validate_required(:age)
|> Changeset.validate_required(:age)
assert changeset.valid? == false
assert changeset.errors == [age: {"can't be blank", [validation: :required]}]
end
test "duplicate validate_inclusion checks do add duplicate errors" do
params = %{"name" => "tyler", "age" => -1}
changeset =
{%Person{}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required(:age)
|> Changeset.validate_inclusion(:age, 0..10)
|> Changeset.validate_inclusion(:age, 0..10)
assert changeset.valid? == false
assert changeset.errors == [
{:age, {"is invalid", [{:validation, :inclusion}, {:enum, 0..10}]}},
{:age, {"is invalid", [{:validation, :inclusion}, {:enum, 0..10}]}}
]
end
end
describe "on a non-empty struct" do
test "when a field is missing from the params, but is present on the struct, it is okay" do
params = %{"name" => "tyler"}
changeset =
{%Person{name: "bob", age: 17}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required([:name, :age])
|> Changeset.validate_inclusion(:age, 0..100) # This will not be run for this case
|> Changeset.validate_inclusion(:age, 13..19) # This will not be run for this case
assert changeset.valid? == true
assert changeset.errors == []
end
test "when an invalid field is present on the struct and the params are empty, the invalid field stays" do
params = %{"name" => "tyler"}
changeset =
{%Person{name: "bob", age: -1}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required([:name, :age])
|> Changeset.validate_inclusion(:age, 0..100)
assert changeset.valid? == true
assert changeset.errors == []
assert %{age: -1} = Changeset.apply_changes(changeset)
end
test "when two fields are required and one was present and the other came from the params, that is okay" do
params = %{"name" => "tyler"}
changeset =
{%Person{age: -1}, @types}
|> Changeset.cast(params, Map.keys(@types))
|> Changeset.validate_required([:name, :age])
|> Changeset.validate_inclusion(:age, 0..100)
assert changeset.valid? == true
assert changeset.errors == []
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment