If you currently aren't testing your code, you really should look into it. Rails makes it fairly easy to do unit testing, and once you get rolling you'll wonder how you lived without it.
Sometimes unit tests can get rather ugly and have a lot of duplication. We've heard about this DRY thing, but does it really apply to test code as well? If you keep your tests DRY through refactoring, you'll start to build a library of helpers which can make testing so much easier. We are going to look into one such refactoring in this article.
First a little background, we are building a Cart engine, and we have a LineItem model with a quantity attribute. Seems pretty normal. In our test we want to make sure the quantity attribute doesn't accept invalid values. So, here's a test case that tries a few invalid values and makes sure they fail:
- def test_invalid_quantity
- item = LineItem.new(:quantity => -1)
- assert !item.valid?
- assert item.errors.invalid?(:quantity)
- item = LineItem.new(:quantity => 'foo')
- assert !item.valid?
- assert item.errors.invalid?(:quantity)
- item = LineItem.new(:quantity => 2.5)
- assert !item.valid?
- assert item.errors.invalid?(:quantity)
- end
Where do we start? This seems like a good place to add a new assertion method. I like to write the interface of the method before actually adding the method because it forces me to think about readability. Let's make a simple assertion to test an invalid value:
- def test_invalid_quantity
- assert_invalid_value LineItem, :quantity, -1
- # ...
- end
- def assert_invalid_value(model_class, attribute, value)
- record = model_class.new(attribute => value)
- assert !record.valid?
- assert record.errors.invalid?(attribute)
- end
- def test_invalid_quantity
- assert_invalid_value LineItem, :quantity, -1
- assert_invalid_value LineItem, :quantity, 'foo'
- assert_invalid_value LineItem, :quantity, 2.5
- end
- def test_invalid_quantity
- assert_invalid_value LineItem, :quantity, [-1, 'foo', 2.5]
- end
- def assert_invalid_value(model_class, attribute, value)
- if value.kind_of? Array
- value.each { |v| assert_invalid_value model_class, attribute, v }
- else
- record = model_class.new(attribute => value)
- assert !record.valid?
- assert record.errors.invalid?(attribute)
- end
- end
- def assert_invalid_value(model_class, attribute, value)
- if value.kind_of? Array
- value.each { |v| assert_invalid_value model_class, attribute, v }
- else
- record = model_class.new(attribute => value)
- assert !record.valid?, "#{model_class} expected to be invalid when #{attribute} is #{value}"
- assert record.errors.invalid?(attribute), "#{attribute} expected to be invalid when set to #{value}"
- end
- end
I can think of another way to make this assert method even more valuable. The interface could look something like this:
- assert_invalid_attributes LineItem, :quantity => [-1, 'foo', 2.5], :product => nil
discuss this topic to forum
