June 12, 2006
Rails Testing Strategies: Valid and Invalid Data
Consider this test…
-
def test_create
-
@item = Item.new
-
assert_valid @item
-
end
Can you tell what the expected result of this test is?
It turns out that you can’t tell from reading this test if it will pass or fail without knowing if ‘Item.new’ returns a valid or invalid object. If ‘Item’ has a ‘validates_presence_of’ statement in there, this new item will be invalid and the test will fail. In addition, this test is ‘fragile’, subtle changes to the model definition can make the result of this test change. This isn’t such a big deal if all you are doing is testing to see if an object is valid or not, but if you are writing a more involved test that depends on this assumption then you might be in trouble.
Currently the only way to ensure that your test data is valid is to add an assertion to each test making the appropriate test. This isn’t very DRY.
One trick that I use to avoid repeating myself a lot in tests is to define a mock object like this..(putting this in a mock object keeps these methods out of production mode).
-
# mocks/test/item.rb
-
require ‘models/item’
-
-
class Item
-
#this assumes that an empty object is invalid
-
#modify this as needed
-
def self.new_invalid
-
Item.new
-
end
-
-
def self.new_valid
-
item = self.new_invalid
-
item.name = "new valid item"
-
# add any additional statements
-
# required to make the item valid
-
end
-
end
Then in my tests I add…
-
def test_valid
-
assert_valid Item.new_valid
-
end
-
-
def test_invalid
-
assert !Item.new_invalid.valid?, "Item not invalid"
-
end
-
-
def test_save_invalid
-
@invalid_item = Item.new_invalid
-
assert !@invalid_item.save, "Saved invalid item"
-
end
This way I am guaranteed to get a valid or invalid item when I need one. in addition, if the definition of valid or invalid changes sufficiently the ‘test_valid’ and ‘test_invalid’ tests will fail, warning me that my assumptions in the rest of the tests are incorrect.
One problem with this approach is if your tests care about HOW the object is invalid. In that case, you might need additional methods like ‘new_with_invalid_name’.
Filed by Kevin Olbrich at 1:36 pm under Ruby on Rails, testing
3 Comments
I’ve been using the following custom assertion to help with negative testing of validates_presence_of:
I’ve been struggling with a lot of the issues for how to TDD the Model elements — don’t want to test the “magic” of AR, but want to test that the desired fields are present in the model, even before getting to the issue of testing validations…. (See my blog for my evolving struggle.)
Thanks for the tip, Kevin.
I tried to use this technique also in development mode so that I can quickly create new data to play with. Just so that others don’t have to spend three hours hunting for the problem: there’s an issue with loading dependencies. It doesn’t matter in testing because because the classes are loaded only once. However, in development mode you need to do this:
class Item
I was trying to say that to prevent such problems you should use require_dependency instead of the plain require.