Sure, but please understand these are still a work in progress and the work of a neophyte ruby coder. Plus they are getting complex enough that I want to split them off to a side project complete with their own suite of tests to test the test.... I find it enjoyable coding, but I shouldn't be so distracted from my main development efforts....
I also need to covert them to give better default failure messages, they should probably be changed to use assert_block in many cases, and I think I should extract a wrapper lambda/block to remove the duplicated code for retrieving the classname and deal with caching the attribute hash.
These currently live in test_helper.rb
class Test::Unit::TestCase
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
protected
def assert_unique_on (field)
klass = class_under_test
cached = @attr_hash.clone
cached[field]=get_reference_fixture.send(field)
current = klass.count
tmp = klass.create(cached)
assert_field_invalid tmp, field
assert_equal current, klass.count
tmp.destroy
end
def assert_attribute_required(field,opt={})
klass = class_under_test
cached = @attr_hash.clone
cached[field]=nil
obj = klass.new(cached)
assert_not_valid obj
assert_field_invalid obj, field
assert_required_length(field,opt[:length],opt) if opt.has_key? :length
assert_unique_on(field) if (opt.has_key?(:unique) && opt[:unique])
assert_confirmation_required(field, opt[:confirmed]) if (opt.has_key?(:confirmed))
end
def assert_confirmation_required(field, confirm)
klass = class_under_test
cached = @attr_hash.clone
cached[confirm]=cached[confirm].swapcase.reverse
obj = klass.new(cached)
assert_not_valid obj
assert_field_invalid obj, field
end
def assert_required_association(field)
field_id = (field.to_s + "_id").to_sym
klass = class_under_test
cached = @attr_hash.clone
obj = klass.new
assert_respond_to obj, ("build_"+field.to_s)
field_id = (field.to_s + "_id").to_sym
cached[field_id]=nil
obj = klass.new(cached)
assert_not_valid obj
assert_field_invalid obj, field_id
end
def assert_optional_association(field, opts)
klass = class_under_test
obj = klass.new
assert_respond_to obj, field
assert_respond_to obj.send(field), :find
assert_instance_of Array, obj.send(field)
end
def assert_not_valid (obj)
assert_block("#{obj.to_s} should not be valid.") { !obj.valid?}
end
def assert_required_length(field,allowed,opt={})
klass =class_under_test
cached = @attr_hash.clone
failing = [allowed.first-1,allowed.last+1]
passing = [allowed.first,allowed.last].uniq
for length in failing do
populate_field_with_fixed_length_string cached, field, length, opt
assert_not_valid klass.new(cached)
end
for length in passing do
populate_field_with_fixed_length_string cached, field, length, opt
assert_valid klass.new(cached)
end
end
def populate_field_with_fixed_length_string(cached, field, length, opt)
cached[field]='a' * length
cached[opt[:confirmed]]=cached[field] if opt.has_key? :confirmed
end
def get_reference_fixture
return class_under_test.find(1)
end
def class_under_test
/([A-Z][A-Za-z_]*)Test/.match(self.class.to_s)
$1.constantize
end
private
def assert_field_invalid(obj, field)
assert_block("#{obj.to_s}'s #{field.to_s} should be invalid.") {obj.errors.invalid?(field)}
end
end
Using them, at present requires one thing -- you have to define a @attr_hash in your test setup. This hash should have a parameter list that can potentially create a new, valid entity, but one that is NOT already in a fixture. Many of the tests take this known good parameter list and then tweak it to create a should fail case. Either by blanking out a field, or grabbing a duplicated value from an arbitrary fixture, etc
Please ignore the "assert_[required|optional]_association" assertions those are initial essays into the solution space, but are no where near solid enough yet.
Last edited by NielsenE (2006-06-18 02:59:57)
My RoR journey -- thoughts on learning RoR and lessons learned in applying TDD and agile practices.