From c7d35c1b540fcd06e406e837b192e09b74570f80 Mon Sep 17 00:00:00 2001 From: Daniela Velasquez Date: Thu, 9 May 2024 21:49:13 +0000 Subject: [PATCH] Adds validations for active model errors Allows asserting that the correct validations were added to a model. Streamline the testing process for validations and make it easier for developers to understand the purpose of each test case. --- activesupport/CHANGELOG.md | 9 ++ .../lib/active_support/testing/assertions.rb | 44 ++++++++ activesupport/test/active_model_test.rb | 103 ++++++++++++++++++ railties/CHANGELOG.md | 2 +- 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 activesupport/test/active_model_test.rb diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index d178e0596ae83..c160842b1d190 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,2 +1,11 @@ +* Adds `assert_error`, `assert_no_error`, `assert_valid` and `assert_not_valid` to ensure a model has the right validations + ```ruby + assert_error :name, :blank, :user + assert_no_error :name, :blank, :user + assert_valid :name, :blank, :user + assert_not_valid :name, :blank, :user + ``` + + *Daniela Velasquez* Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 8d484a78f1347..3c80ba5455244 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -261,6 +261,50 @@ def assert_no_changes(expression, message = nil, from: UNTRACKED, &block) retval end + # Assertion that an active model doesn't have an error on a field + # + # assert_no_error :name, :blank, :user + def assert_no_error(attribute, type, obj, 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_empty obj.errors.where(attribute, type), msg + end + + # Assertion that an active model has a specific error on a field + # + # assert_error :name, :blank, :user + def assert_error(attribute, type, obj, 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 be %s" % data + } + refute_empty obj.errors.where(attribute, type), msg + end + + # Assertion that an active model's attribute is not valid after validation + # + # assert_not_valid :name, :blank, :user + def assert_not_valid(attribute, type, obj, msg = nil) + raise ArgumentError.new("#{obj.inspect} does not respond to #validate") unless obj.respond_to?(:validate) + + obj.validate + assert_error(attribute, type, obj, msg) + end + + # Assertion that an active model's attribute is valid after validation + # + # assert_valid :name, :blank, :user + def assert_valid(attribute, type, obj, msg = nil) + raise ArgumentError.new("#{obj.inspect} does not respond to #validate") unless obj.respond_to?(:validate) + + obj.validate + assert_no_error(attribute, type, obj, msg) + end + private def _assert_nothing_raised_or_warn(assertion, &block) assert_nothing_raised(&block) diff --git a/activesupport/test/active_model_test.rb b/activesupport/test/active_model_test.rb new file mode 100644 index 0000000000000..b722d094d2ec2 --- /dev/null +++ b/activesupport/test/active_model_test.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require_relative "abstract_unit" +require "active_model" + +class ActiveModelTest < 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 + end.new + end + + test "#assert_no_error asserts active model does not have an error on a field" do + @active_model.name = "name" + @active_model.validate + + assert_no_error :name, :blank, @active_model + end + + test "#assert_no_error raises ArgumentError with a non-active record" do + error = assert_raises(ArgumentError) do + assert_no_error :name, :blank, Object.new + end + + assert_includes error.message, "does not respond to #errors" + end + + test "#assert_no_error raises a Minitest::Assertion when validation fails" do + @active_model.validate + error = assert_raises(Minitest::Assertion) do + assert_no_error :name, :blank, @active_model + end + assert_includes error.message, "Expected name to not be blank" + end + + test "#assert_error asserts active model has an error on a field" do + @active_model.validate + assert_error :name, :blank, @active_model + end + + test "#assert_error raises ArgumentError with a non-active record" do + error = assert_raises(ArgumentError) do + assert_error :name, :blank, Object.new + end + + assert_includes error.message, "does not respond to #errors" + end + + test "#assert_error raises a Minitest::Assertion when validation fails" do + @active_model.name = "h" + @active_model.validate + error = assert_raises(Minitest::Assertion) do + assert_error :name, :blank, @active_model + end + assert_includes error.message, "Expected name to be blank" + end + + test "#assert_not_valid asserts active model has error on a field after validation" do + assert_not_valid :name, :blank, @active_model + end + + test "#assert_not_valid raises ArgumentError with message about the object nor responding to validate" do + error = assert_raises(ArgumentError) do + assert_not_valid :name, :blank, Object.new + end + + assert_includes error.message, "does not respond to #validate" + end + + test "#assert_not_valid raises a Minitest::Assertion when validation fails" do + @active_model.name = "Hi" + error = assert_raises(Minitest::Assertion) do + assert_not_valid :name, :blank, @active_model + end + assert_includes error.message, "Expected name to be blank" + end + + test "#assert_valid asserts active model does not have an error on a field after validation" do + @active_model.name = "name" + + assert_valid :name, :blank, @active_model + end + + test "#assert_valid raises ArgumentError with message about the object nor responding to validate" do + error = assert_raises(ArgumentError) do + assert_valid :name, :blank, Object.new + end + + assert_includes error.message, "does not respond to #validate" + end + + test "#assert_valid raises a Minitest::Assertion when validation fails" do + error = assert_raises(Minitest::Assertion) do + assert_valid :name, :blank, @active_model + end + assert_includes error.message, "Expected name to not be blank" + end +end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f20cd1dda26df..2a96b943760f2 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,6 +1,6 @@ * Use Kamal for deployment by default, which includes generating a Rails-specific config/deploy.yml. This can be skipped using --skip-kamal. See more: https://kamal-deploy.org/ - *DHH* + *DHH* Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/railties/CHANGELOG.md) for previous changes.