diff --git a/README b/README
index d52257a8..c73d4b31 100644
--- a/README
+++ b/README
@@ -4,12 +4,13 @@ A collection of Test::Unit helper methods.
Adds helpers for
-#. Contexts and should statements
-#. Common ActiveRecord model tests
-#. A few general purpose assertions
-
+1. context and should statements
+1. Common ActiveRecord model tests
+1. A few general purpose assertions
+
== Todo
-#. Controller test helpers
-#. General code cleanups
-#. More options for AR helpers
+1. Controller test helpers
+1. General code cleanups
+1. More options for AR helpers
+
\ No newline at end of file
diff --git a/Rakefile b/Rakefile
index d567f3d8..53bc807f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -8,6 +8,16 @@ Rake::TestTask.new do |t|
t.verbose = true
end
+# Generate the RDoc documentation
+
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "ThoughtBot Test Helpers -- Making your tests easy on the fingers and eyes"
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.rdoc_files.include('README', 'lib/**/*.rb')
+}
+
desc 'Default: run tests.'
task :default => ['test']
diff --git a/lib/color.rb b/lib/color.rb
index a6ae137d..36afa07d 100644
--- a/lib/color.rb
+++ b/lib/color.rb
@@ -1,19 +1,27 @@
require 'test/unit/ui/console/testrunner'
# Completely stolen from redgreen gem
-module Color
- COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 }
- def self.method_missing(color_name, *args)
+#
+# Adds colored output to your tests. Specify color: true in
+# your test/tb_test_helpers.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.
+# A fix would be most welcome.
+#
+module Color
+ COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 } # :nodoc:
+ def self.method_missing(color_name, *args) # :nodoc:
color(color_name) + args.first + color(:clear)
end
- def self.color(color)
+ def self.color(color) # :nodoc:
"\e[#{COLORS[color.to_sym]}m"
end
end
-module Test
- module Unit
- class TestResult
+module Test # :nodoc:
+ module Unit # :nodoc:
+ class TestResult # :nodoc:
alias :old_to_s :to_s
def to_s
if old_to_s =~ /\d+ tests, \d+ assertions, (\d+) failures, (\d+) errors/
@@ -22,7 +30,7 @@ module Test
end
end
- class AutoRunner
+ class AutoRunner # :nodoc:
alias :old_initialize :initialize
def initialize(standalone)
old_initialize(standalone)
@@ -32,7 +40,7 @@ module Test
end
end
- class Failure
+ class Failure # :nodoc:
alias :old_long_display :long_display
def long_display
# old_long_display.sub('Failure', Color.red('Failure'))
@@ -40,7 +48,7 @@ module Test
end
end
- class Error
+ class Error # :nodoc:
alias :old_long_display :long_display
def long_display
# old_long_display.sub('Error', Color.yellow('Error'))
@@ -48,9 +56,9 @@ module Test
end
end
- module UI
- module Console
- class RedGreenTestRunner < Test::Unit::UI::Console::TestRunner
+ module UI # :nodoc:
+ module Console # :nodoc:
+ class RedGreenTestRunner < Test::Unit::UI::Console::TestRunner # :nodoc:
def output_single(something, level=NORMAL)
return unless (output?(level))
something = case something
diff --git a/lib/context.rb b/lib/context.rb
new file mode 100644
index 00000000..c237dca8
--- /dev/null
+++ b/lib/context.rb
@@ -0,0 +1,120 @@
+module TBTestHelpers # :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 can contain setup, should, should_eventually, and teardown blocks.
+ #
+ # 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
+ # end
+ # end
+ #
+ # This code will produce the method "test a User instance should return its full name" (yes, with spaces in the 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.
+ #
+ # 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
+ #
+ # 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.)
+ #
+
+ module Context
+ def Context.included(other) # :nodoc:
+ @@context_names = []
+ @@setup_blocks = []
+ @@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)
+ def should(name, opts = {}, &should_block)
+ test_name = ["test", @@context_names, "should", "#{name}"].flatten.join(' ').to_sym
+
+ name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding)
+ raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined
+
+ setup_blocks = @@setup_blocks.dup
+ teardown_blocks = @@teardown_blocks.dup
+
+ if opts[:unimplemented]
+ define_method test_name do |*args|
+ # XXX find a better way of doing this.
+ assert true
+ STDOUT.putc "X" # Tests for this model are missing.
+ end
+ else
+ define_method test_name do |*args|
+ begin
+ setup_blocks.each {|b| b.bind(self).call }
+ should_block.bind(self).call(*args)
+ ensure
+ teardown_blocks.reverse.each {|b| b.bind(self).call }
+ end
+ end
+ end
+ end
+
+ # Defines a specification that is not yet implemented.
+ # Will be displayed as an 'X' when running tests, and failures will not be shown.
+ def should_eventually(name, &block)
+ should("eventually #{name}", {:unimplemented => true}, &block)
+ end
+ end
+end
diff --git a/lib/should.rb b/lib/should.rb
deleted file mode 100644
index 5384cec2..00000000
--- a/lib/should.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-module TBTestHelpers # :nodoc:
- module Should
- def Should.included(other) # :nodoc:
- @@context_names = []
- @@setup_blocks = []
- @@teardown_blocks = []
- end
-
- # Creates a context block with the given name. The context block can contain setup, should, should_eventually, and teardown blocks.
- 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 specification. Can be called either inside our outside of a context.
- def should(name, opts = {}, &should_block)
- test_name = ["test", @@context_names, "should", "#{name}"].flatten.join(' ').to_sym
-
- name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding)
- raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined
-
- setup_blocks = @@setup_blocks.dup
- teardown_blocks = @@teardown_blocks.dup
-
- if opts[:unimplemented]
- define_method test_name do |*args|
- # XXX find a better way of doing this.
- assert true
- STDOUT.putc "X" # Tests for this model are missing.
- end
- else
- define_method test_name do |*args|
- begin
- setup_blocks.each {|b| b.bind(self).call }
- should_block.bind(self).call(*args)
- ensure
- teardown_blocks.reverse.each {|b| b.bind(self).call }
- end
- end
- end
- end
-
- # Defines a specification that is not yet implemented. Will be displayed as an 'X' when running tests, and failures will not be shown.
- def should_eventually(name, &block)
- should("eventually #{name}", {:unimplemented => true}, &block)
- end
- end
-end
diff --git a/lib/tb_test_helpers.rb b/lib/tb_test_helpers.rb
index 73dc0656..ae168ed1 100644
--- a/lib/tb_test_helpers.rb
+++ b/lib/tb_test_helpers.rb
@@ -1,5 +1,5 @@
require 'active_record_helpers'
-require 'should'
+require 'context'
require 'yaml'
config_file = "tb_test_helpers.conf"
@@ -14,7 +14,7 @@ module Test # :nodoc:
class << self
include TBTestHelpers::Should
- # Loads all fixture files
+ # Loads all fixture files (test/fixtures/*.yml)
def load_all_fixtures
all_fixtures = Dir.glob(File.join(RAILS_ROOT, "test", "fixtures", "*.yml")).collect do |f|
File.basename(f, '.yml').to_sym
@@ -24,12 +24,16 @@ module Test # :nodoc:
end
- # Logs a message, tagged with TESTING: and the name of the calling method.
+ # 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
@@ -37,12 +41,13 @@ module Test # :nodoc:
assert_equal initial_value + difference, object.send(method), (msg || "#{object}##{method} after block")
end
- # Ensures that object.method does not change
+ # 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.
+ # 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}.") }
@@ -54,27 +59,26 @@ module Test # :nodoc:
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.
+ # assert_contains(['a', '1'], /\d/) => passes
def assert_contains(collection, x, extra_msg = "")
collection = [collection] unless collection.is_a?(Array)
msg = "#{x} not found in #{collection.to_a.inspect} " + extra_msg
case x
when Regexp: assert(collection.detect { |e| e =~ x }, msg)
- when String: assert(collection.include?(x), msg)
- when Fixnum: assert(collection.include?(x), msg)
- else
- raise ArgumentError, "Don't know what to do with #{x}"
+ 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} found in #{collection.to_a.inspect} " + extra_msg
case x
when Regexp: assert(!collection.detect { |e| e =~ x }, msg)
- when String: assert(!collection.include?(x), msg)
- when Fixnum: assert(!collection.include?(x), msg)
- else
- raise ArgumentError, "Don't know what to do with #{x}"
+ else assert(!collection.include?(x), msg)
end
end