Test Ash Validation


Here's a way to test a validation module in Ash framework.

The resource

Let's say that we have an Ash.Resource defined. Here are the bits that matter. There's a resource with an attribute, genre_id, that's defined as a relationship. There is an action for :create. It has a validate macro in its body. That references a custom validation module.

defmodule MyLibrary.Book do
  use Ash.Resource
    # ...

  attributes do
    # ...
  end

  relationships do
    # ...

    belongs_to :genre, MyLibrary.Genre do
      allow_nil? false
      public? true
    end
  end

  actions do
    defaults [:read]

    create :create do
      accept [:genre_id]
      validate MyLibrary.IsGreatGenre
    end
  end
end

The validation

The validation module looks something like this. It's getting the input genre_id, making a database lookup, then making a genre record data comparison in order to validate:

defmodule MyLibrary.IsGreatGenre do
  use Ash.Resource.Validation

  @impl true
  def validate(changeset, _opts, _context) do
    genre_id = Ash.Changeset.get_argument_or_attribute(changeset, :genre_id)
    genre = Ash.get!(MyLibrary.Genre, genre_id)
    if genre.greatness_level in ["great", "super", "magnificent"] do
      :ok
    else
      {:error, field: "genre_id", message: "Only great genres allowed"}
    end
  end
end

The validation test

Well, what genres would pass this strictly-great validator? Let's make a test!

defmodule MyLibrary.IsGreatGenreTest do
  use MyLibrary.DataCase,
    async: false

  test "not great genre" do
    genre = GenreFixtures.at_greatness_level!("lowly")

    changeset = Ash.Changeset.for_create(MyLibrary.Book, :create, %{
      genre_id: genre.id
    })

    assert %Ash.Changeset{
            valid?: false, 
            errors: [
              %Ash.Error.Changes.InvalidAttribute{
                field: "genre_id", 
                message: "Only great genres allowed"
              }
            ]
          } = changeset
  end

  test "is great genre" do
    genre = GenreFixtures.at_greatness_level!("magnificent")

    changeset = Ash.Changeset.for_create(MyLibrary.Book, :create, %{
      genre_id: genre.id
    })

    assert %Ash.Changeset{valid?: true, errors: []} = changeset
  end
end

There are two cases: valid and not valid. When you create an Ash.Changeset, it will be validated and call each validator in the action.

Validated. Tested. Done.