# Collection of various assertions and other methods, commonly used in Rails unit tests # To debug a test, put 'bp' anywhere and run the test. # It will spawn IRB session at the scope where bp is. # Requires either breakpoint or dev-utils package begin require 'breakpoint' rescue LoadError begin require 'dev-utils/debug' rescue LoadError begin require_gem 'dev-utils' rescue end end end # Shorthand for IRB breakpoint alias :bp :breakpoint if defined? Breakpoint # Assert that an object has all of the listed attributes def assert_has_attributes(object, attributes) assert object.kind_of?(ActiveRecord::Base) assert_nothing_raised { attributes.each { |attr| object.send(attr.to_sym) } } end # Remember start and finish time of a block, then test that a certain field was set to sysdate) # Usage: # post = time { Post.create(test_inputs) } # assert_equal_sysdate post.created_at begin def time @start_time = Time.now return yield ensure @finish_time = Time.now end def assert_equal_sysdate(value) assert value.to_i.between?(@start_time.to_i, @finish_time.to_i), "'#{value}' is #{offset_from_sysdate(value)} msec off from the last remembered sysdate" end def offset_from_sysdate(value) value.to_i - (@start_time.to_i + @finish_time.to_i) / 2 end end # For a given list of attribute names, asserts that all of them are not nullable, # and it is enforced by validate def assert_mandatory_attributes_enforced(attribute_names, entity_class) # it should be possible to create a record from prototype_params assert_nothing_raised { p = entity_class.create(prototype_params(entity_class)) } attribute_names.each do |attr| params = prototype_params(entity_class).dup params[attr] = nil assert_raises(ArgumentError, "Attribute #{attr} can be set to nil") do entity_class.new(params).validate end end end # Asserts that all attributes specified in the attributes_hash have the same value in the ar_object def assert_attributes_equal(attributes_hash, ar_object) attributes_hash.each_pair { |k, v| if k == 'id' and v.nil? #skip the check; prototype attributes always include a nil id else assert_equal v, ar_object[k], "Value of '#{k} attribute is not as expected" end } end # Extracts attributes from the ar_ibject def attributes(ar_object) # this doesn't work because internal representation of numbers in AR::Base is strings: # ar_object.instance_eval('@attributes').dup hash = {} ar_object.attribute_names.each { |attr| hash[attr] = ar_object[attr] } hash end def assert_validation_fails(clazz, attributes, error_field = nil, expected_message = nil) begin clazz.create(attributes) fail "Validation did not fail" rescue RForum::ValidationError => expected assert expected.errors[error_field] if error_field if expected_message assert_equal expected_message, expected.errors[error_field], "Error message not as expected" end end end # Prototypes are pre-set objects that can be used to create a test fixture quickly module FtaPrototypes PROTOTYPE_ATTRIBUTES = {} def self.set_prototype(clazz, attributes) # prototype attributes should have an 'id' attribute, but it should be nil. # This is how it is coming from HTML forms when the same form is reused for creation # and editing of an entity. Code that works with attribute hashes must hande this situation # correctly. attributes['id'] = nil attributes.each_pair { |key, value| key.freeze; value.freeze } attributes.freeze PROTOTYPE_ATTRIBUTES[clazz.to_s] = PrototypeAttrs.new(attributes) end def self.set_prototypes(prototypes_hash) prototypes_hash.each_pair { |key, value| set_prototype(key, value) } end class PrototypeAttrs < Hash def initialize(hash) hash.each_pair { |k, v| self[k] = v } end # Deletes attrs from prototype # Usage: # post = prototype_params(Post).without('l', 'r') def without(*attrs) attrs.each { |attr| self.delete attr } self end end end # Returns an (attributes => values) hash that can be used to create a new object of given class # If additional_params is specified, merges them into the hash # # Usage examples: # normal_post = Post.create(prototype_params(Post)) # post_with_empty_text = Post.new(prototype_params(Post, 'text' => nil)) # errors = prototype_params(Post) { |params| # begin; Post.create(params); rescue ValidationError => e; end # } def prototype_params(clazz, additional_params = nil) raise "No prototype for #{clazz}" if FtaPrototypes::PROTOTYPE_ATTRIBUTES[clazz.to_s].nil? prototype_hash = FtaPrototypes::PROTOTYPE_ATTRIBUTES[clazz.to_s].dup if prototype_hash.nil? raise "#{clazz.inspect} not found among prototypes " + "for #{FtaPrototypes::PROTOTYPE_ATTRIBUTES.keys.collect{|k| k.to_s}.join(', ')}" end combined_hash = prototype_hash.merge(additional_params ? additional_params : {} ) yield combined_hash if block_given? combined_hash end # Same as prototype_params above, but returns a result of passing the prototype parameters hash # to clazz.new. Also, if a block is given, it receives an object from constructor, not # a parameters hash def prototype(clazz, additional_params = nil) new_object = Object::const_get(clazz.to_s).new(prototype_params(clazz, additional_params)) yield new_object if block_given? new_object end