Menu
+

Enums Testing
Testing Enums on Models
rails
minitest
postgresql
Guest
Friday, 04 April 25




Testing enums on Models is tricky as I found out. 

I was building a cart system for my new project and I decided to add a enums for status as follow: 

class Cart < ApplicationRecord
  belongs_to :user
  has_many :cart_items, dependent: :destroy

  enum :status, { active: "active", completed: "completed", cancelled: "cancelled" }

  validates :status, presence: true, inclusion: { in: statuses.keys }

  def total_price
    cart_items.sum { |item| item.product.price * item.quantity }
  end

  def total_items
    cart_items.sum(:quantity)
  end
end



And written simple test for invalid status as follow:

  test "should not allow invalid status" do
    cart = Cart.new(status: "invalid_status")
    assert_not cart.save, "Cart should not be saved with an invalid status"
    assert_includes cart.errors[:status], "is not included in the list", "Status validation should enforce valid statuses"
  end

Unfortunately this was throwing ArgumentError.  

Error:
CartTest#test_should_not_allow_invalid_status:
ArgumentError: 'invalid_status' is not a valid status
    test/models/cart_test.rb:38:in `block in <class:CartTest>'

This is because rails raises an ArgumentError immediately if an invalid value is assigned for enums, preventing the test from reaching the validation step. 

After some research I found a hack to override the setter to catches the error and writes the value directly to the attribute as follow:

  def status=(value)
    super
  rescue ArgumentError
    @attributes.write_cast_value("status", value)
  end

This will add an error instead of crashing with ArgumentError allowing validation to occur. Now this was the approach prior to Rails 7.1 but now with 7.1+ we can simply validate on enum it self. 

 enum :status, { active: "active", completed: "completed", cancelled: "cancelled" }, validate: true

Its simple as that and just works out of the box. You could also just validate on column it self:

validates_inclusion_of :status, in: [:active, :completed, :cancelled]

But keep in mind if you do this you wont be able to use suffixs or use helper methods like Cart.is_cart_active?  no scopes like Cart.active and no type casting.