Skip to content

Commit

Permalink
Adds assert_error_on and assert_no_error_on assertions
Browse files Browse the repository at this point in the history
Introduces two new assertions, `assert_error_on` and `assert_no_error_on`, to simplify checking for specific validation errors on models.

Example usage:
  - assert_error_on user, :name, :blank
  - assert_no_error_on user, :name, :blank

This enhances test readability and makes validation testing more intuitive.
  • Loading branch information
DanielaVelasquez committed Oct 12, 2024
1 parent b050366 commit ee2e91e
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 0 deletions.
9 changes: 9 additions & 0 deletions activesupport/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
* Added new test assertions `assert_error_on` and `assert_no_error_on` to simplify testing for specific validation errors on models.
Example usage:
```ruby
assert_error_on user :name, :blank
assert_no_error user, :name, :blank
```

*Daniela Velasquez*

* Fix `ActiveSupport::HashWithIndifferentAccess#stringify_keys` to stringify all keys not just symbols.

Previously:
Expand Down
80 changes: 80 additions & 0 deletions activesupport/lib/active_support/testing/assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,86 @@ def assert_no_changes(expression, message = nil, from: UNTRACKED, &block)
retval
end

# Asserts that an ActiveModel object has a specific error on a given attribute.
#
# This assertion checks whether a validation error of a specific type
# exists on the provided attribute of the object. If the error does not
# exist, the test will fail, displaying an optional custom error message or
# a default one indicating the expected and actual outcomes.
#
# Example:
#
# assert_error_on(user, :name, :blank)
# # Asserts that the `user` object has a `:blank` validation error on the `:name` attribute.
#
# Arguments:
# obj: The ActiveModel object being validated. It must respond to `#errors`.
# attribute: The attribute for which the validation error is expected (e.g., `:name`).
# type: The specific type of validation error (e.g., `:blank`, `:invalid`, etc.).
# msg: An optional custom message to display if the assertion fails. If not provided,
# a default message is generated based on the provided attribute and error type.
#
# Raises:
# ArgumentError: If the `obj` does not respond to `#errors`.
#
# Example Custom Error Message:
#
# assert_error_on(user, :name, :blank, "Expected 'name' to be present but it's blank.")
#
# Failing Example:
#
# assert_error_on(user, :email, :invalid)
# # Fails if `user.errors` does not contain an `:invalid` error for the `:email` attribute.
def assert_error_on(obj, attribute, type, msg = nil)
raise ArgumentError.new("#{obj.inspect} does not respond to #errors") unless obj.respond_to?(:errors)

msg = message(msg) {
data = [type, attribute]
"Expected error %s on %s" % data
}

assert(obj.errors.added?(attribute, type), msg)
end

# Asserts that an ActiveModel object does not have a specific error on a given attribute.
#
# This assertion checks that a validation error of a specific type is not present
# on the provided attribute of the object. If the error exists, the test will fail,
# displaying an optional custom error message or a default one indicating the
# unexpected error on the attribute.
#
# Example:
#
# assert_no_error_on(user, :name, :blank)
# # Asserts that the `user` object does not have a `:blank` validation error on the `:name` attribute.
#
# Arguments:
# obj: The ActiveModel object being validated. It must respond to `#errors`.
# attribute: The attribute for which the absence of a specific validation error is expected (e.g., `:name`).
# type: The specific type of validation error that is expected *not* to be present (e.g., `:blank`, `:invalid`).
# msg: An optional custom message to display if the assertion fails. If not provided,
# a default message is generated based on the provided attribute and error type.
#
# Raises:
# ArgumentError: If the `obj` does not respond to `#errors`.
#
# Example Custom Error Message:
#
# assert_no_error_on(user, :name, :blank, "Expected 'name' to be valid and not blank.")
#
# Failing Example:
#
# assert_no_error_on(user, :email, :invalid)
# # Fails if `user.errors` contains an `:invalid` error for the `:email` attribute.
def assert_no_error_on(obj, attribute, type, msg = nil)
raise ArgumentError.new("#{obj.inspect} does not respond to #errors") unless obj.respond_to?(:errors)
msg = message(msg) {
data = [attribute, type]
"Expected %s to not be %s" % data
}
assert_not(obj.errors.added?(attribute, type), msg)
end

private
def _assert_nothing_raised_or_warn(assertion, &block)
assert_nothing_raised(&block)
Expand Down
77 changes: 77 additions & 0 deletions activesupport/test/validation_errors_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# frozen_string_literal: true

require_relative "abstract_unit"
require "active_model"

class ValidationErrorsTest < ActiveSupport::TestCase
def setup
@active_model = Class.new do
include ActiveModel::Model
include ActiveModel::Attributes

attribute :name, :string
attribute :last_name, :string
validates :name, :last_name, presence: true
validate :name_doesnt_contain_numbers

private
def name_doesnt_contain_numbers
unless name.nil? || name.scan(/\d/).empty?
errors.add(:name, "shouldn't contain numbers")
end
end
end.new
end

test "#assert_no_error_on asserts active model does not have an error on a field" do
@active_model.name = "name"
@active_model.validate

assert_no_error_on @active_model, :name, :blank
end

test "#assert_no_error_on raises ArgumentError with an object that doesn't respond to errors" do
error = assert_raises(ArgumentError) do
assert_no_error_on Object.new, :name, :blank, Object.new
end

assert_includes error.message, "does not respond to #errors"
end

test "#assert_no_error_on raises a Minitest::Assertion when validation fails" do
@active_model.validate
error = assert_raises(Minitest::Assertion) do
assert_no_error_on @active_model, :name, :blank
end
assert_includes error.message, "Expected name to not be blank"
end

test "#assert_error_on asserts active model has an error on name field" do
@active_model.validate
assert_error_on @active_model, :name, :blank
end

test "#assert_error_on asserts active model has an error on a field with a string" do
error_message = "must start with H"
@active_model.errors.add(:name, error_message)

assert_error_on @active_model, :name, error_message
end

test "#assert_error_on raises ArgumentError on an object that doesn't respond to errors" do
error = assert_raises(ArgumentError) do
assert_error_on :name, :blank, Object.new
end

assert_includes error.message, "does not respond to #errors"
end

test "#assert_error_on raises a Minitest::Assertion when validation fails" do
@active_model.name = "h"
@active_model.validate
error = assert_raises(Minitest::Assertion) do
assert_error_on @active_model, :name, :blank
end
assert_includes error.message, "Expected error blank on name"
end
end

0 comments on commit ee2e91e

Please sign in to comment.