Skip to content

Commit

Permalink
Fix exist matcher so that it uses either #exist? or #exists?
Browse files Browse the repository at this point in the history
  • Loading branch information
myronmarston authored and dchelimsky committed Jan 17, 2011
1 parent 27ca1f4 commit 12d1ae3
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 58 deletions.
13 changes: 9 additions & 4 deletions features/built_in_matchers/exist.feature
@@ -1,11 +1,11 @@
Feature: exist matcher

The exist matcher is used to specify that something exists
(as indicated by #exist?):
(as indicated by #exist? or #exists?):

obj.should exist # passes if obj.exist?
obj.should exist # passes if obj.exist? or obj.exists?

Scenario: basic usage
Scenario Outline: basic usage
Given a file named "exist_matcher_spec.rb" with:
"""
class Planet
Expand All @@ -19,7 +19,7 @@ Feature: exist matcher
"<Planet: #{name}>"
end
def exist?
def <predicate_method>
%w[Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune].include?(name)
end
end
Expand All @@ -42,3 +42,8 @@ Feature: exist matcher
| expected <Planet: Earth> not to exist |
| expected <Planet: Tatooine> to exist |

Examples:
| predicate_method |
| exist? |
| exists? |

16 changes: 13 additions & 3 deletions lib/rspec/matchers/exist.rb
Expand Up @@ -4,11 +4,21 @@ module Matchers
# should exist
# should_not exist
#
# Passes if actual.exist?
def exist(arg=nil)
# Passes if actual.exist? or actual.exists?
def exist(*args)
Matcher.new :exist do
match do |actual|
arg ? actual.exist?(arg) : actual.exist?
predicates = [:exist?, :exists?].select { |p| actual.respond_to?(p) }
existance_values = predicates.map { |p| actual.send(p, *args) }
uniq_truthy_values = existance_values.map { |v| !!v }.uniq

case uniq_truthy_values.size
when 0; raise NoMethodError.new("#{actual.inspect} does not respond to either #exist? or #exists?")
when 1; existance_values.first
else raise "#exist? and #exists? returned different values:\n\n" +
" exist?: #{existance_values.first}\n" +
"exists?: #{existance_values.last}"
end
end
end
end
Expand Down
141 changes: 90 additions & 51 deletions spec/rspec/matchers/exist_spec.rb
@@ -1,65 +1,104 @@
require 'spec_helper'

class Substance
def initialize exists, description
@exists = exists
@description = description
end
def exist?(arg=nil)
@exists
end
def inspect
@description
end
end

class SubstanceTester
include RSpec::Matchers
def initialize substance
@substance = substance
end
def should_exist
@substance.should exist
end
end
describe "exist matcher" do
context "when the object does not respond to #exist? or #exists?" do
subject { mock }

describe "should exist" do

before(:each) do
@real = Substance.new true, 'something real'
@imaginary = Substance.new false, 'something imaginary'
[:should, :should_not].each do |should_method|
describe "#{should_method} exist" do
it "raises an error" do
expect {
subject.send(should_method, exist)
}.to raise_error(NoMethodError)
end
end
end
end

describe "within an example group" do

it "passes if target exists" do
@real.should exist
end

it "passes if target exists with args" do
@real.should exist('this arg')
end

it "fails if target does not exist" do
lambda { @imaginary.should exist }.should fail
end

it "describes itself" do
exist.description.should == "exist"
end

it "passes should_not exist if target doesn't exist" do
lambda { @real.should_not exist }.should fail
[:exist?, :exists?].each do |predicate|
context "when the object responds to ##{predicate}" do
describe "should exist" do
it "passes if #{predicate}" do
mock(predicate => true).should exist
end

it "fails if not #{predicate}" do
expect {
mock(predicate => false).should exist
}.to fail_with(/expected .* to exist/)
end
end

describe "should not exist" do
it "passes if not #{predicate}" do
mock(predicate => false).should_not exist
end

it "fails if #{predicate}" do
expect {
mock(predicate => true).should_not exist
}.to fail_with(/expected .* not to exist/)
end
end
end
end

describe "outside of an example group" do
context "when the object responds to #exist? and #exists?" do
context "when they both return falsey values" do
subject { mock(:exist? => false, :exists? => nil) }

describe "should_not exist" do
it "passes" do
subject.should_not exist
end
end

describe "should exist" do
it "fails" do
expect {
subject.should exist
}.to fail_with(/expected .* to exist/)
end
end
end

context "when they both return truthy values" do
subject { mock(:exist? => true, :exists? => "something true") }

describe "should_not exist" do
it "fails" do
expect {
subject.should_not exist
}.to fail_with(/expected .* not to exist/)
end
end

it "passes if target exists" do
real_tester = SubstanceTester.new @real
real_tester.should_exist
describe "should exist" do
it "passes" do
subject.should exist
end
end
end

context "when they return values with different truthiness" do
subject { mock(:exist? => true, :exists? => false) }

[:should, :should_not].each do |should_method|
describe "#{should_method} exist" do
it "raises an error" do
expect {
subject.send(should_method, exist)
}.to raise_error(/#exist\? and #exists\? returned different values/)
end
end
end
end
end

it 'passes any provided arguments to the call to #exist?' do
object = mock
object.should_receive(:exist?).with(:foo, :bar) { true }

object.should exist(:foo, :bar)
end
end

0 comments on commit 12d1ae3

Please sign in to comment.