Permalink
Browse files

The should method now takes a matcher; added a should_not method for

negative matcher tests.
  • Loading branch information...
1 parent 572fa89 commit 08efc476790b7e4f386f18a1b0405f017501fd5d @jferris jferris committed Feb 12, 2010
Showing with 205 additions and 9 deletions.
  1. +47 −8 lib/shoulda/context.rb
  2. +158 −1 test/other/context_test.rb
View
@@ -21,9 +21,10 @@ def remove_context # :nodoc:
module ClassMethods
# == Should statements
#
- # Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
- # contains all the normal code and assertions you're used to seeing, with the added benefit that
- # they can be wrapped inside context blocks (see below).
+ # Should statements are just syntactic sugar over normal Test::Unit test
+ # methods. A should block contains all the normal code and assertions
+ # you're used to seeing, with the added benefit that they can be wrapped
+ # inside context blocks (see below).
#
# === Example:
#
@@ -56,19 +57,44 @@ module ClassMethods
# assert true
# end
# end
+ #
+ # Should statements can also wrap matchers, making virtually any matcher
+ # usable in a macro style. The matcher's description is used to generate a
+ # test name and failure message, and the test will pass if the matcher
+ # matches the subject.
+ #
+ # === Example:
+ #
+ # should validate_presence_of(:first_name).with_message(/gotta be there/)
+ #
- def should(name, options = {}, &blk)
+ def should(name_or_matcher, options = {}, &blk)
if Shoulda.current_context
- block_given? ? Shoulda.current_context.should(name, options, &blk) : Shoulda.current_context.should_eventually(name)
+ Shoulda.current_context.should(name_or_matcher, options, &blk)
else
context_name = self.name.gsub(/Test/, "")
context = Shoulda::Context.new(context_name, self) do
- block_given? ? should(name, options, &blk) : should_eventually(name)
+ should(name_or_matcher, options, &blk)
end
context.build
end
end
+ # Allows negative tests using matchers. The matcher's description is used
+ # to generate a test name and negative failure message, and the test will
+ # pass unless the matcher matches the subject.
+ #
+ # === Example:
+ #
+ # should_not set_the_flash
+ def should_not(matcher)
+ context_name = self.name.gsub(/Test/, "")
+ context = Shoulda::Context.new(context_name, self) do
+ should_not(matcher)
+ end
+ context.build
+ end
+
# == Before statements
#
# Before statements are should statements that run before the current
@@ -288,14 +314,27 @@ def teardown(&blk)
self.teardown_blocks << blk
end
- def should(name, options = {}, &blk)
- if block_given?
+ def should(name_or_matcher, options = {}, &blk)
+ if name_or_matcher.respond_to?(:description) && name_or_matcher.respond_to?(:matches?)
+ name = name_or_matcher.description
+ blk = lambda { assert_accepts name_or_matcher, subject }
+ else
+ name = name_or_matcher
+ end
+
+ if blk
self.shoulds << { :name => name, :before => options[:before], :block => blk }
else
self.should_eventuallys << { :name => name }
end
end
+ def should_not(matcher)
+ name = matcher.description
+ blk = lambda { assert_rejects matcher, subject }
+ self.shoulds << { :name => "not #{name}", :block => blk }
+ end
+
def should_eventually(name, &blk)
self.should_eventuallys << { :name => name, :block => blk }
end
View
@@ -174,6 +174,163 @@ class ::SomeModel; end
end
end
+class ShouldMatcherTest < Test::Unit::TestCase
+ class FakeMatcher
+ attr_reader :subject
+ attr_accessor :fail
+
+ def description
+ "do something"
+ end
+
+ def matches?(subject)
+ @subject = subject
+ !@fail
+ end
+
+ def failure_message
+ "a failure message"
+ end
+
+ def negative_failure_message
+ "not a failure message"
+ end
+ end
+
+ def run_test
+ @test_suite.run(@test_result) { |event, name |}
+ end
+
+ def setup
+ @matcher = FakeMatcher.new
+ @test_result = Test::Unit::TestResult.new
+ class << @test_result
+ def failure_messages
+ @failures.map { |failure| failure.message }
+ end
+ end
+ end
+
+ def create_test_suite(&definition)
+ test_class = Class.new(Test::Unit::TestCase, &definition)
+ test_class.suite
+ end
+
+ def assert_failed_with(message, test_result)
+ assert_equal 1, test_result.failure_count
+ assert_equal [message], test_result.failure_messages
+ end
+
+ def assert_passed(test_result)
+ assert_equal 0, test_result.failure_count
+ end
+
+ def assert_test_named(expected_name, test_suite)
+ name = test_suite.tests.map { |test| test.method_name }.first
+ assert name.include?(expected_name), "Expected #{name} to include #{expected_name}"
+ end
+
+ def self.should_use_positive_matcher
+ should "generate a test using the matcher's description" do
+ assert_test_named "should #{@matcher.description}", @test_suite
+ end
+
+ should "pass with a passing matcher" do
+ @matcher.fail = false
+ run_test
+ assert_passed @test_result
+ end
+
+ should "fail with a failing matcher" do
+ @matcher.fail = true
+ run_test
+ assert_failed_with @matcher.failure_message, @test_result
+ end
+
+ should "provide the subject" do
+ @matcher.fail = false
+ run_test
+ assert_equal 'a subject', @matcher.subject
+ end
+ end
+
+ def self.should_use_negative_matcher
+ should "generate a test using the matcher's description" do
+ assert_test_named "should not #{@matcher.description}", @test_suite
+ end
+
+ should "pass with a failing matcher" do
+ @matcher.fail = true
+ run_test
+ assert_passed @test_result
+ end
+
+ should "fail with a passing matcher" do
+ @matcher.fail = false
+ run_test
+ assert_failed_with @matcher.negative_failure_message, @test_result
+ end
+
+ should "provide the subject" do
+ @matcher.fail = false
+ run_test
+ assert_equal 'a subject', @matcher.subject
+ end
+ end
+
+ context "a should block with a matcher" do
+ setup do
+ matcher = @matcher
+ @test_suite = create_test_suite do
+ subject { 'a subject' }
+ should matcher
+ end
+ end
+
+ should_use_positive_matcher
+ end
+
+ context "a should block with a matcher within a context" do
+ setup do
+ matcher = @matcher
+ @test_suite = create_test_suite do
+ context "in context" do
+ subject { 'a subject' }
+ should matcher
+ end
+ end
+ end
+
+ should_use_positive_matcher
+ end
+
+ context "a should_not block with a matcher" do
+ setup do
+ matcher = @matcher
+ @test_suite = create_test_suite do
+ subject { 'a subject' }
+ should_not matcher
+ end
+ end
+
+ should_use_negative_matcher
+ end
+
+ context "a should_not block with a matcher within a context" do
+ setup do
+ matcher = @matcher
+ @test_suite = create_test_suite do
+ context "in context" do
+ subject { 'a subject' }
+ should_not matcher
+ end
+ end
+ end
+
+ should_use_negative_matcher
+ end
+end
+
class Subject; end
class SubjectTest < ActiveSupport::TestCase
@@ -195,4 +352,4 @@ class SubjectLazinessTest < ActiveSupport::TestCase
should "only build the subject once" do
assert_equal subject, subject
end
-end
+end

0 comments on commit 08efc47

Please sign in to comment.