diff --git a/README b/README index 192fd54c..3632e5cb 100644 --- a/README +++ b/README @@ -1,17 +1,123 @@ -= ThoughtBot Test Helpers += Shoulda -A collection of Test::Unit helper methods. +Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use. -Adds helpers for +Helpers:: #context and #should give you rSpec like test blocks. + In addition, you get nested contexts and a much more readable syntax. +Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros. + They get you started quickly, and can help you ensure that your application is conforming to best practices. +Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions. -* context and should statements -* Common ActiveRecord model tests -* A few general purpose assertions += Usage + +=== Context Helpers (ThoughtBot::Shoulda::Context) + +Stop killing your fingers with all of those underscores... Name your tests with plain sentences! + + class UserTest << Test::Unit + context "A User instance" do + setup do + @user = User.find(:first) + end + + should "return its full name" + assert_equal 'John Doe', @user.full_name + end + + context "with a profile" do + setup do + @user.profile = Profile.find(:first) + end + + should "return true when sent #has_profile?" + assert @user.has_profile? + end + end + end + end + +Produces the following test methods: + + "test: A User instance should return its full name." + "test: A User instance with a profile should return true when sent #has_profile?." + +So readable! + +=== ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord) + +Quick macro tests for your ActiveRecord associations and validations: + + class PostTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :user + should_have_many :tags, :through => :taggings + + should_require_unique_attributes :title + should_require_attributes :body, :message => /wtf/ + should_require_attributes :title + should_only_allow_numeric_values_for :user_id + end + + class UserTest < Test::Unit::TestCase + load_all_fixtures + + should_have_many :posts + + should_not_allow_values_for :email, "blah", "b lah" + should_allow_values_for :email, "a@b.com", "asdf@asdf.com" + should_ensure_length_in_range :email, 1..100 + should_ensure_value_in_range :age, 1..100 + should_protect_attributes :password + end + +Makes TDD so much easier. + +=== Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods) + +Macros to test the most common controller patterns... + + context "on GET to :show for first record" do + setup do + get :show, :id => 1 + end -== Todo + should_assign_to :user + should_respond_with :success + should_render_template :show + should_not_set_the_flash -* Many more tests (of the tests). Specifically, the ActiveRecord macros need to be tested. -* Controller test helpers -* General code cleanups -* More options for AR helpers - \ No newline at end of file + should "do something else really cool" do + assert_equal 1, assigns(:user).id + end + end + +Test entire controllers in a few lines... + + class PostsControllerTest < Test::Unit::TestCase + should_be_restful do |resource| + resource.parent = :user + + resource.create.params = { :title => "first post", :body => 'blah blah blah'} + resource.update.params = { :title => "changed" } + end + end + +should_be_restful generates 40 tests on the fly, for both html and xml requests. + +=== Helpful Assertions (ThoughtBot::Shoulda::General) + +More to come here, but have fun with what's there. + + load_all_fixtures + assert_same_elements([:a, :b, :c], [:c, :a, :b]) + assert_contains(['a', '1'], /\d/) + assert_contains(['a', '1'], 'a') + += Credits + +Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc. + += License + +Shoulda is Copyright © 2006-2007 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the README file of the Ruby distribution. \ No newline at end of file diff --git a/lib/shoulda.rb b/lib/shoulda.rb index b72c27d7..a549e0b9 100644 --- a/lib/shoulda.rb +++ b/lib/shoulda.rb @@ -22,9 +22,9 @@ end require 'shoulda/color' if shoulda_options[:color] -module Test # :nodoc: - module Unit # :nodoc: - class TestCase # :nodoc: +module Test # :nodoc: all + module Unit + class TestCase include ThoughtBot::Shoulda::Controller include ThoughtBot::Shoulda::General @@ -38,10 +38,10 @@ module Test # :nodoc: end end -module ActionController #:nodoc: - module Integration #:nodoc: - class Session #:nodoc: - include ThoughtBot::Shoulda::General::InstanceMethods +module ActionController #:nodoc: all + module Integration + class Session + include ThoughtBot::Shoulda::General end end end diff --git a/lib/shoulda/color.rb b/lib/shoulda/color.rb index 0a1a777e..1ccfad2b 100644 --- a/lib/shoulda/color.rb +++ b/lib/shoulda/color.rb @@ -3,7 +3,7 @@ require 'test/unit/ui/console/testrunner' # Completely stolen from redgreen gem # # Adds colored output to your tests. Specify color: true in -# your test/shoulda.conf file to enable. +# your ~/.shoulda.conf file to enable. # # *Bug*: for some reason, this adds another line of output to the end of # every rake task, as though there was another (empty) set of tests. diff --git a/lib/shoulda/context.rb b/lib/shoulda/context.rb index 753963f3..12d820e3 100644 --- a/lib/shoulda/context.rb +++ b/lib/shoulda/context.rb @@ -2,9 +2,9 @@ module ThoughtBot # :nodoc: module Shoulda # :nodoc: # = context and should blocks # - # A context block can exist next to normal def test_blah statements, - # meaning you do not have to fully commit to the context/should syntax in a test file. We have been - # using this syntax at ThoughtBot, though, and find it very readable. + # A context block groups should statements under a common setup/teardown method. + # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability + # and readability of your test code. # # A context block can contain setup, should, should_eventually, and teardown blocks. # @@ -20,7 +20,7 @@ module ThoughtBot # :nodoc: # end # end # - # This code will produce the method "test a User instance should return its full name" (yes, with spaces in the name). + # This code will produce the method "test a User instance should return its full name". # # Contexts may be nested. Nested contexts run their setup blocks from out to in before each test. # They then run their teardown blocks from in to out after each test. @@ -48,8 +48,11 @@ module ThoughtBot # :nodoc: # end # # This code will produce the following methods - # * "test a User instance should return its full name" - # * "test a User instance with a profile should return true when sent :has_profile?" (which will have both setup blocks run before it.) + # * "test: a User instance should return its full name." + # * "test: a User instance with a profile should return true when sent :has_profile?." + # + # A context block can exist next to normal def test_the_old_way; end tests, + # meaning you do not have to fully commit to the context/should syntax in a test file. # module Context @@ -59,32 +62,20 @@ module ThoughtBot # :nodoc: @@teardown_blocks = [] end - # Creates a context block with the given name. - def context(name, &context_block) - saved_setups = @@setup_blocks.dup - saved_teardowns = @@teardown_blocks.dup - saved_contexts = @@context_names.dup - - @@context_names << name - context_block.bind(self).call - - @@context_names = saved_contexts - @@setup_blocks = saved_setups - @@teardown_blocks = saved_teardowns - end - - # Run before every should block in the current context - def setup(&setup_block) - @@setup_blocks << setup_block - end - - # Run after every should block in the current context - def teardown(&teardown_block) - @@teardown_blocks << teardown_block - end - - # Defines a test. Can be called either inside our outside of a context. - # Optionally specify :unimplimented => true (see should_eventually) + # Defines a test method. Can be called either inside our outside of a context. + # Optionally specify :unimplimented => true (see should_eventually). + # + # Example: + # + # class UserTest << Test::Unit + # should "return first user on find(:first)" + # assert_equal users(:first), User.find(:first) + # end + # end + # + # Would create a test named + # 'test: should return first user on find(:first)' + # def should(name, opts = {}, &should_block) test_name = ["test:", @@context_names, "should", "#{name}. "].flatten.join(' ').to_sym @@ -112,8 +103,38 @@ module ThoughtBot # :nodoc: end end + # Creates a context block with the given name. + def context(name, &context_block) + saved_setups = @@setup_blocks.dup + saved_teardowns = @@teardown_blocks.dup + saved_contexts = @@context_names.dup + + @@context_names << name + context_block.bind(self).call + + @@context_names = saved_contexts + @@setup_blocks = saved_setups + @@teardown_blocks = saved_teardowns + end + + # Run before every should block in the current context. + # If a setup block appears in a nested context, it will be run after the setup blocks + # in the parent contexts. + def setup(&setup_block) + @@setup_blocks << setup_block + end + + # Run after every should block in the current context. + # If a teardown block appears in a nested context, it will be run before the teardown + # blocks in the parent contexts. + def teardown(&teardown_block) + @@teardown_blocks << teardown_block + end + # Defines a specification that is not yet implemented. # Will be displayed as an 'X' when running tests, and failures will not be shown. + # This is equivalent to: + # should(name, {:unimplemented => true}, &block) def should_eventually(name, &block) should("eventually #{name}", {:unimplemented => true}, &block) end diff --git a/lib/shoulda/controller_tests/controller_tests.rb b/lib/shoulda/controller_tests/controller_tests.rb index c9085df6..ddf05807 100644 --- a/lib/shoulda/controller_tests/controller_tests.rb +++ b/lib/shoulda/controller_tests/controller_tests.rb @@ -1,6 +1,5 @@ module ThoughtBot # :nodoc: module Shoulda # :nodoc: - # = Macro test helpers for your controllers module Controller def self.included(other) # :nodoc: other.class_eval do @@ -12,6 +11,30 @@ module ThoughtBot # :nodoc: end end + # = Macro test helpers for your controllers + # + # By using the macro helpers you can quickly and easily create concise and easy to read test suites. + # + # This code segment: + # context "on GET to :show for first record" do + # setup do + # get :show, :id => 1 + # end + # + # should_assign_to :user + # should_respond_with :success + # should_render_template :show + # should_not_set_the_flash + # + # should "do something else really cool" do + # assert_equal 1, assigns(:user).id + # end + # end + # + # Would produce 5 tests for the +show+ action + # + # Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your + # controller responds restfully to a variety of requested formats. module ClassMethods # Formats tested by #should_be_restful. Defaults to [:html, :xml] VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc: @@ -20,26 +43,108 @@ module ThoughtBot # :nodoc: # Actions tested by #should_be_restful VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc: + # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller. + # + # Example: + # class UsersControllerTest < Test::Unit::TestCase + # load_all_fixtures + # + # def setup + # ...normal setup code... + # @user = User.find(:first) + # end + # + # should_be_restful do |resource| + # resource.identifier = :id + # resource.klass = User + # resource.object = :user + # resource.parent = [] + # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy] + # resource.formats = [:html, :xml] + # + # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13} + # resource.update.params = { :name => "sue" } + # + # resource.create.redirect = "user_url(@user)" + # resource.update.redirect = "user_url(@user)" + # resource.destroy.redirect = "users_url" + # + # resource.create.flash = /created/i + # resource.update.flash = /updated/i + # resource.destroy.flash = /removed/i + # end + # end + # + # Whenever possible, the resource attributes will be set to sensible defaults. + # class ResourceOptions + # Configuration options for the create, update, destroy actions under should_be_restful class ActionOptions - # String eval'd to get the target of the redirection + # String evaled to get the target of the redirection. + # All of the instance variables set by the controller will be available to the + # evaled code. + # + # Example: + # resource.create.redirect = "user_url(@user.company, @user)" + # + # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list. attr_accessor :redirect - # String or Regexp describing a value expected in the flash + # String or Regexp describing a value expected in the flash. Will match against any flash key. + # + # Defaults: + # destroy:: /removed/ + # create:: /created/ + # update:: /updated/ attr_accessor :flash - # Hash describing the params that should be sent in with this action + # Hash describing the params that should be sent in with this action. attr_accessor :params - - # Actions that should be denied (only used by resource.denied) + end + + # Configuration options for the denied actions under should_be_restful + # + # Example: + # context "The public" do + # setup do + # @request.session[:logged_in] = false + # end + # + # should_be_restful do |resource| + # resource.parent = :user + # + # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy] + # resource.denied.flash = /get outta here/i + # resource.denied.redirect = 'new_session_url' + # end + # end + # + class DeniedOptions + # String evaled to get the target of the redirection. + # All of the instance variables set by the controller will be available to the + # evaled code. + # + # Example: + # resource.create.redirect = "user_url(@user.company, @user)" + attr_accessor :redirect + + # String or Regexp describing a value expected in the flash. Will match against any flash key. + # + # Example: + # resource.create.flash = /created/ + attr_accessor :flash + + # Actions that should be denied (only used by resource.denied). Note that these actions will + # only be tested if they are also listed in +resource.actions+ attr_accessor :actions end - # Name of key in params that references the primary key. Will almost always be :id (default) + # Name of key in params that references the primary key. + # Will almost always be :id (default), unless you are using a plugin or have patched rails. attr_accessor :identifier # Name of the ActiveRecord class this resource is responsible for. Automatically determined from - # test class if not explicitly set. + # test class if not explicitly set. UserTest => :user attr_accessor :klass # Name of the instantiated ActiveRecord object that should be used by some of the tests. @@ -47,33 +152,62 @@ module ThoughtBot # :nodoc: attr_accessor :object # Name of the parent AR objects. + # + # Example: + # # in the routes... + # map.resources :companies do + # map.resources :people do + # map.resources :limbs + # end + # end + # + # # in the tests... + # class PeopleControllerTest < Test::Unit::TestCase + # should_be_restful do |resource| + # resource.parent = :companies + # end + # end + # + # class LimbsControllerTest < Test::Unit::TestCase + # should_be_restful do |resource| + # resource.parents = [:companies, :people] + # end + # end attr_accessor :parent alias parents parent alias parents= parent= - # Actions that should be tested. Must be a subset of #VALID_ACTIONS + # Actions that should be tested. Must be a subset of VALID_ACTIONS (default). + # Tests for each actionw will only be generated if the action is listed here. + # + # Example (for a read-only controller): + # resource.actions = [:show, :index] attr_accessor :actions - # Formats that should be tested. Must be a subset of #VALID_FORMATS + # Formats that should be tested. Must be a subset of VALID_FORMATS (default). + # Each action will be tested against the formats listed here. + # + # Example: + # resource.actions = [:html, :xml] attr_accessor :formats - # ActionOptions object + # ActionOptions object specifying options for the create action. attr_accessor :create - # ActionOptions object + # ActionOptions object specifying options for the update action. attr_accessor :update - # ActionOptions object + # ActionOptions object specifying options for the desrtoy action. attr_accessor :destroy - # ActionOptions object + # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case. attr_accessor :denied def initialize # :nodoc: @create = ActionOptions.new @update = ActionOptions.new @destroy = ActionOptions.new - @denied = ActionOptions.new + @denied = DeniedOptions.new @actions = VALID_ACTIONS @formats = VALID_FORMATS @denied.actions = [] @@ -119,7 +253,31 @@ module ThoughtBot # :nodoc: end end - # Bunch of documentation and examples for this one. + # :section: should_be_restful + # Generates a full suite of tests for a restful controller. + # + # The following definition will generate tests for the +index+, +show+, +new+, + # +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats: + # + # should_be_restful do |resource| + # resource.parent = :user + # + # resource.create.params = { :title => "first post", :body => 'blah blah blah'} + # resource.update.params = { :title => "changed" } + # end + # + # This generates about 40 tests, all of the format: + # "on GET to :show should assign @user." + # "on GET to :show should not set the flash." + # "on GET to :show should render 'show' template." + # "on GET to :show should respond with success." + # "on GET to :show as xml should assign @user." + # "on GET to :show as xml should have ContentType set to 'application/xml'." + # "on GET to :show as xml should respond with success." + # "on GET to :show as xml should return as the root element." + # The +resource+ parameter passed into the block is a ResourceOptions object, and + # is used to configure the tests for the details of your resources. + # def should_be_restful(&blk) # :yields: resource resource = ResourceOptions.new blk.call(resource) @@ -138,8 +296,14 @@ module ThoughtBot # :nodoc: end end + # :section: Test macros + # Macro that creates a test asserting that the flash contains the given value. # val can be a String or a Regex + # + # Example: + # + # should_set_the_flash_to /created/i def should_set_the_flash_to(val) should "have #{val.inspect} in the flash" do assert_contains flash.values, val, ", Flash: #{flash.inspect}" @@ -154,6 +318,10 @@ module ThoughtBot # :nodoc: end # Macro that creates a test asserting that the controller assigned to @name + # + # Example: + # + # should_assign_to :user def should_assign_to(name) should "assign @#{name}" do assert assigns(name.to_sym), "The show action isn't assigning to @#{name}" @@ -161,6 +329,10 @@ module ThoughtBot # :nodoc: end # Macro that creates a test asserting that the controller did not assign to @name + # + # Example: + # + # should_not_assign_to :user def should_not_assign_to(name) should "not assign to @#{name}" do assert !assigns(name.to_sym), "@#{name} was visible" @@ -187,8 +359,12 @@ module ThoughtBot # :nodoc: end end + # Macro that creates a test asserting that the controller returned a redirect to the given path. + # Example: + # + # should_redirect_to "/" def should_redirect_to(url) - should "redirect to #{url}" do + should "redirect to \"#{url}\"" do instantiate_variables_from_assigns do assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__) end @@ -196,9 +372,9 @@ module ThoughtBot # :nodoc: end end - module InstanceMethods - - private + module InstanceMethods # :nodoc: + + private # :enddoc: SPECIAL_INSTANCE_VARIABLES = %w{ _cookies @@ -248,11 +424,8 @@ module ThoughtBot # :nodoc: def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc: parent_names ||= resource.parents.reverse - return {} if parent_names == [] # Base case - parent_name = parent_names.shift - parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first) { :"#{parent_name}_id" => parent.id }.merge(make_parent_params(resource, parent, parent_names)) diff --git a/lib/shoulda/controller_tests/formats/xml.rb b/lib/shoulda/controller_tests/formats/xml.rb index 2fcc9293..6e10e2a7 100644 --- a/lib/shoulda/controller_tests/formats/xml.rb +++ b/lib/shoulda/controller_tests/formats/xml.rb @@ -1,8 +1,8 @@ module ThoughtBot # :nodoc: module Shoulda # :nodoc: module Controller # :nodoc: - module XML # :nodoc: - def self.included(other) + module XML + def self.included(other) #:nodoc: other.class_eval do extend ThoughtBot::Shoulda::Controller::XML::ClassMethods end @@ -27,7 +27,7 @@ module ThoughtBot # :nodoc: protected - def make_show_xml_tests(res) + def make_show_xml_tests(res) # :nodoc: context "on GET to :show as xml" do setup do request_xml @@ -47,15 +47,15 @@ module ThoughtBot # :nodoc: end end - def make_edit_xml_tests(res) + def make_edit_xml_tests(res) # :nodoc: # XML doesn't need an :edit action end - def make_new_xml_tests(res) + def make_new_xml_tests(res) # :nodoc: # XML doesn't need a :new action end - def make_index_xml_tests(res) + def make_index_xml_tests(res) # :nodoc: context "on GET to :index as xml" do setup do request_xml @@ -74,7 +74,7 @@ module ThoughtBot # :nodoc: end end - def make_destroy_xml_tests(res) + def make_destroy_xml_tests(res) # :nodoc: context "on DELETE to :destroy as xml" do setup do request_xml @@ -97,7 +97,7 @@ module ThoughtBot # :nodoc: end end - def make_create_xml_tests(res) + def make_create_xml_tests(res) # :nodoc: context "on POST to :create as xml" do setup do request_xml @@ -123,7 +123,7 @@ module ThoughtBot # :nodoc: end end - def make_update_xml_tests(res) + def make_update_xml_tests(res) # :nodoc: context "on PUT to :update as xml" do setup do request_xml diff --git a/lib/shoulda/general.rb b/lib/shoulda/general.rb index 284a7974..6131bdca 100644 --- a/lib/shoulda/general.rb +++ b/lib/shoulda/general.rb @@ -1,10 +1,10 @@ module ThoughtBot # :nodoc: module Shoulda # :nodoc: - module General # :nodoc: - def self.included(other) + module General + def self.included(other) # :nodoc: other.class_eval do extend ThoughtBot::Shoulda::General::ClassMethods - include ThoughtBot::Shoulda::General::InstanceMethods + # include ThoughtBot::Shoulda::General::InstanceMethods end end @@ -18,69 +18,67 @@ module ThoughtBot # :nodoc: end end - module InstanceMethods - # Prints a message to stdout, tagged with the name of the calling method. - def report!(msg = "") - puts("#{caller.first}: #{msg}") + # Prints a message to stdout, tagged with the name of the calling method. + def report!(msg = "") + puts("#{caller.first}: #{msg}") + end + + # Ensures that the number of items in the collection changes + # + # assert_difference(User, :count, 1) { User.create } + # assert_difference(User.packages, :size, 3, true) { User.add_three_packages } + # + # Setting reload to true will call object.reload after the block (for ActiveRecord associations) + def assert_difference(object, method, difference, reload = false, msg = nil) + initial_value = object.send(method) + yield + object.send(:reload) if reload + assert_equal initial_value + difference, object.send(method), (msg || "#{object}##{method} after block") + end + + # Ensures that object.method does not change. See assert_difference for usage. + def assert_no_difference(object, method, reload = false, msg = nil, &block) + assert_difference(object, method, 0, reload, msg, &block) + end + + # Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered. + # + # assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes + def assert_same_elements(a1, a2, msg = nil) + [:select, :inject, :size].each do |m| + [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") } end - # Ensures that the number of items in the collection changes - # - # assert_difference(User, :count, 1) { User.create } - # assert_difference(User.packages, :size, 3, true) { User.add_three_packages } - # - # Setting reload to true will call object.reload after the block (for ActiveRecord associations) - def assert_difference(object, method, difference, reload = false, msg = nil) - initial_value = object.send(method) - yield - object.send(:reload) if reload - assert_equal initial_value + difference, object.send(method), (msg || "#{object}##{method} after block") - end + assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h } + assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h } - # Ensures that object.method does not change. See assert_difference for usage. - def assert_no_difference(object, method, reload = false, msg = nil, &block) - assert_difference(object, method, 0, reload, msg, &block) - end + assert_equal(a1h, a2h, msg) + end - # Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered. - # - # assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes - def assert_same_elements(a1, a2, msg = nil) - [:select, :inject, :size].each do |m| - [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") } - end + # Asserts that the given collection contains item x. If x is a regular expression, ensure that + # at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails. + # + # assert_contains(['a', '1'], /\d/) => passes + # assert_contains(['a', '1'], 'a') => passes + # assert_contains(['a', '1'], /not there/) => fails + def assert_contains(collection, x, extra_msg = "") + collection = [collection] unless collection.is_a?(Array) + msg = "#{x.inspect} not found in #{collection.to_a.inspect} " + extra_msg + case x + when Regexp: assert(collection.detect { |e| e =~ x }, msg) + else assert(collection.include?(x), msg) + end + end - assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h } - assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h } - - assert_equal(a1h, a2h, msg) - end - - # Asserts that the given collection contains item x. If x is a regular expression, ensure that - # at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails. - # - # assert_contains(['a', '1'], /\d/) => passes - # assert_contains(['a', '1'], 'a') => passes - # assert_contains(['a', '1'], /not there/) => fails - def assert_contains(collection, x, extra_msg = "") - collection = [collection] unless collection.is_a?(Array) - msg = "#{x.inspect} not found in #{collection.to_a.inspect} " + extra_msg - case x - when Regexp: assert(collection.detect { |e| e =~ x }, msg) - else assert(collection.include?(x), msg) - end - end - - # Asserts that the given collection does not contain item x. If x is a regular expression, ensure that - # none of the elements from the collection match x. - def assert_does_not_contain(collection, x, extra_msg = "") - collection = [collection] unless collection.is_a?(Array) - msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg - case x - when Regexp: assert(!collection.detect { |e| e =~ x }, msg) - else assert(!collection.include?(x), msg) - end - end + # Asserts that the given collection does not contain item x. If x is a regular expression, ensure that + # none of the elements from the collection match x. + def assert_does_not_contain(collection, x, extra_msg = "") + collection = [collection] unless collection.is_a?(Array) + msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg + case x + when Regexp: assert(!collection.detect { |e| e =~ x }, msg) + else assert(!collection.include?(x), msg) + end end end end