Tip nhanh: Không lặp code khi viết Test Validations Model

Tôi đã làm việc trên các ứng dụng và cần viết test cho các model. Tôi đã phải lặp lại các bài test validation cho mỗi trường, mỗi model dẫn đến việc code bị trùng lặp. Vì vậy, tôi sẽ chia sẻ giải pháp cho vấn đề này, sẽ giúp chúng ta tránh lặp lại test cho các validation tương tự nhau trong mỗi model.

Tôi dùng Ruby 2.3.0, Rails 4.2.4. , Minitest và FactoryGirl trong vài viết này. Tôi đã viết một đoạn test cho model Site như sau:

class SiteTest < ActiveSupport::TestCase
  def test_should_require_customer_name
    site = Site.new
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:customer_name)
    assert site.errors.messages[:customer_name].include?("can't be blank")
  end

  def test_should_require_customer_email
    site = Site.new
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:customer_email)
    assert site.errors.messages[:customer_email].include?("can't be blank")
  end

  def test_should_require_host
    site = Site.new
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:host)
    assert site.errors.messages[:host].include?("can't be blank")
  end

  def test_should_require_host_to_be_unique
    theme = FactoryGirl.create(:theme)
    Site.skip_callback(:create, :after, :setup_components)
    existing_site = FactoryGirl.create(:site, theme: theme)
    Site.after_create(:setup_components)
    site = Site.new(host: existing_site.host)
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:host)
    assert site.errors.messages[:host].include?("has already been taken")
  end

  def test_should_require_theme
    site = Site.new
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:theme)
    assert site.errors.messages[:theme].include?("can't be blank")
  end

  def test_should_require_user
    site = Site.new
    refute site.valid?
    refute site.save
    assert_operator site.errors.count, :>, 0
    assert site.errors.messages.include?(:user)
    assert site.errors.messages[:user].include?("can't be blank")
  end

end

Kết quả test:

$ ruby -Ilib:test test/models/site_test.rb
SiteTest
 test_should_require_user PASS (0.45s)
 test_should_require_host PASS (0.01s)
 test_should_require_customer_email PASS (0.01s)
 test_should_require_host_to_be_unique PASS (0.09s)
 test_should_require_theme PASS (0.01s)
 test_should_require_customer_name PASS (0.01s)
Finished in 0.58104s
6 tests, 30 assertions, 0 failures, 0 errors, 0 skips

Quá nhiều code giống nhau cho tính năng test đơn giản. Hãy tưởng tượng nếu bạn phải lặp lại nó trong tất cả các model cho tất cả các trường bạn muốn validation.

Tôi không biết liệu có Gem nào DRY điều này hay không, vì tôi không tìm giải pháp cho vấn đề này. Thay vào đó, tôi bắt đầu sử dụng Ruby để khắc phục vấn đề này. Và đây là những gì tôi đã đưa ra:

module TestModelValidations
  def self.included(klass)
    klass.class_eval do

      def self.test_validates_presence_of(*args)
        args.each do |field_name|
          define_method("test_should_require_#{field_name.to_s}") do
            model = self.class.model_klass.new
            assert_validation(model, field_name, "can't be blank")
          end
        end
      end

      def self.test_validates_uniqueness_of(existing_model, *args)
        args.each do |field_name|
          define_method("test_should_require_#{field_name.to_s}_to_be_unique") do
            params_hash = {}
            params_hash[field_name] = existing_model.send(field_name)
            model = self.class.model_klass.new(params_hash)
            assert_validation(model, field_name, "has already been taken")
          end
        end
      end

    private
      def assert_validation(model, field_name, error_message)
        refute model.valid?
        refute model.save
        assert_operator model.errors.count, :>, 0
        assert model.errors.messages.include?(field_name)
        assert model.errors.messages[field_name].include?(error_message)
      end

    end
  end

  def model_klass
    self.class.name.underscore.split("_test").first.camelize.constantize
  end
end

Bạn có thể đặt file này trong test/support/ và require tất cả file trong thư mục support.Trước khi bắt đầu test thêm dòng này vào file test/test_helper.rb:

Dir[Rails.root.join(‘test’, ‘support’,*.rb’)].each { |f| require f }

Bạn chỉ cần include module này trong mỗi file test để sử dụng phiên bản DRYed up của các bài test validation:

class ActiveSupport::TestCase
 ActiveRecord::Migration.check_pending!
end

Mở class ActiveSupport :: TestCase và extends nó, đây là class mà mỗi class test model kế thừa. Thêm dòng này vào đó:

include TestModelValidations

Bây giờ class trông như sau:

class ActiveSupport::TestCase
 ActiveRecord::Migration.check_pending!
 include TestModelValidations
end

Bây giờ chúng tôi đã sẵn sàng để trình bày phiên bản DRIed up:

class SiteTest < ActiveSupport::TestCase
  test_validates_presence_of :customer_email, :customer_name, :host, :theme, :user
  test_validates_uniqueness_of FactoryGirl.create(:site), :host
end

Vâng, chính là nó 😃. Đó là tất cả những gì cần để test model validations. Hãy tưởng tượng việc này được sử dụng này trong tất cả test model của bạn, hy vọng rằng nó sẽ giúp bạn tiết kiệm hàng trăm dòng code và nỗ lực sao chép/dán. Hãy kiểm tra lại để đảm bảo rằng mọi thứ đang hoạt động như trước đây:

$ ruby -Ilib:test test/models/site_test.rb
SiteTest
 test_should_require_customer_name PASS (0.34s)
 test_should_require_user PASS (0.01s)
 test_should_require_host PASS (0.01s)
 test_should_require_host_to_be_unique PASS (0.01s)
 test_should_require_theme PASS (0.01s)
 test_should_require_customer_email PASS (0.01s)
Finished in 0.39483s
6 tests, 30 assertions, 0 failures, 0 errors, 0 skips

Boom! Tất cả mọi thứ hoạt động như mong đợi, nhưng là code sạch và thuận tiện hơn. Điều này làm cho việc test validation cho model dễ dàng hơn.

test_validates_uniqueness_of FactoryGirl.create(:site), :host

Lệnh validates này xác nhận tính duy nhất của một trường trong model. Đối số đầu tiên là trường hợp test. Sau đối số đầu tiên, là trường của model bạn muón xác định tính duy nhất của trương hợp đó.

Mong rằng sẽ nhận được nhận xét và câu hỏi từ bạn. Tôi thực sự hy vọng điều này sẽ giúp ích cho bạn!

Thank you!

Tài liệu dịch: https://www.sitepoint.com/quick-tip-dry-up-your-model-validations-tests/