Is there an `errors_on` like function for `Ash.Error`?

Ash does not seem to provide an errors_on like function you’d normally get in a Phoenix project template, and I’m in my test trying to validate failure expectations.

Any recommendations for how I should be extracting error messages from Ash.Error during tests?

    test "fails: with a short password" do
      email = "mike@mikezornek.com"
      password = "short"
      password_confirmation = "short"

      assert {:error, ash_error} =
               User.register_with_password(
                 email,
                 password,
                 password_confirmation
               )

      
      # normally I'd do something like:
      # assert "should be at least 8 character(s)" in errors_on(changeset).password

      # Q: What function should I lean on to extract error strings per field from `Ash.Error`?
      dbg(ash_error)
      assert false
    end
1 Like

:wave: So there are two things you can do. In Ash, we have a concept of “error classes”, there are currently four of them: Ash.Error.Forbidden, Ash.Error.Invalid, Ash.Error.Framework, and Ash.Error.Unknown. These are “error containers”. Since multiple errors can occur within a single action invocation, we use these containers to get a general sense of what went wrong. We choose these errors based on the “worst” (in the order I listed) error. i.e if we get a single error of class :forbidden, then we return the Ash.Error.Forbidden error class, and include within it all errors that occurred.

We talk more about these errors here: Handle Errors — ash v2.15.19

Now, to the specific question of asserting on testing, 99 times out of 100 I would prefer to do something to cause the error to raise. When a class error is raised, the message contains the message from every error inside it.

So I would typically prefer:

assert_raise Ash.Error.Invalid, ~r/price: must be greater than 0/, fn -> 
  # do the thing
end

However, if you want to just run changeset validations and make your assertions, you have to do one of two things:

  1. make assertions about the returned errors like this:
assert %Ash.Error.Invalid{errors: errors} = Ash.Error.to_error_class(changeset.errors)
assert Enum.any?(errors, fn error -> ... end)
  1. raise the error
assert_raise Ash.Error.Invalid, ~r/your message/, fn -> 
  raise Ash.Error.to_error_class(changeset.errors)
end

Additionally, I’ve just added some helpers for this in the main branch of ash, Ash.Test.assert_has_error and Ash.Test.refute_has_error

      Ash.Test.assert_has_error(cs, Ash.Error.Unknown, fn err ->
        err.error == "whoops!"
      end)

      Ash.Test.refute_has_error(cs, Ash.Error.Unknown, fn err ->
        err.error == "yay!"
      end)
5 Likes