From 3cde006fdb4ea9653f9ba51eef1c5f87aae785a2 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 | 123 ++++++++++++++++++ 3 files changed, 176 insertions(+) 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..e84b2e324ddf8 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_not(obj.errors.added?(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 = [type, attribute] + "Expected error %s on %s" % data + } + assert(obj.errors.added?(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..0753a771f9828 --- /dev/null +++ b/activesupport/test/active_model_test.rb @@ -0,0 +1,123 @@ +# 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 + 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 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 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 :name, error_message, @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 error blank on name" + 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 asserts active model has error on a field with a string message" do + @active_model.name = "Person1" + assert_not_valid :name, "shouldn't contain numbers", @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 error blank on name" + 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