Skip to content

Commit

Permalink
Merge pull request #412 from pedrogimenez/be_between_exclusive
Browse files Browse the repository at this point in the history
Implement exclusive mode in be_between
  • Loading branch information
JonRowe committed Jan 8, 2014
2 parents 75d5d77 + f4edebd commit fc697d0
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 69 deletions.
4 changes: 3 additions & 1 deletion lib/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,14 @@ def be_a_kind_of(expected)
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
# Float, Complex, and Rational).
#
# @note Inclusive of both min and max values.
# By default, `be_between` is inclusive (i.e. passes when given either the max or min value),
# but you can make it `exclusive` by chaining that off the matcher.
#
# @example
#
# expect(5).to be_between(1, 10)
# expect(11).not_to be_between(1, 10)
# expect(10).not_to be_between(1, 10).exclusive
def be_between(min, max)
BuiltIn::BeBetween.new(min, max)
end
Expand Down
27 changes: 23 additions & 4 deletions lib/rspec/matchers/built_in/be_between.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ module BuiltIn
class BeBetween < BaseMatcher
def initialize(min, max)
@min, @max = min, max
inclusive
end

def inclusive
@less_than_operator = :<=
@greater_than_operator = :>=
@mode = :inclusive
self
end

def exclusive
@less_than_operator = :<
@greater_than_operator = :>
@mode = :exclusive
self
end

def matches?(actual)
@actual = actual
comparable? and @actual.between?(@min, @max)
comparable? && compare
rescue ArgumentError
false
end
Expand All @@ -18,17 +33,21 @@ def failure_message
end

def description
"be between #{@min.inspect} and #{@max.inspect} (inclusive)"
"be between #{@min.inspect} and #{@max.inspect} (#{@mode})"
end

private

def comparable?
@actual.respond_to?(:between?)
@actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator)
end

def not_comparable_clause
", but it does not respond to `between?`" unless comparable?
", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable?
end

def compare
@actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max)
end
end
end
Expand Down
199 changes: 135 additions & 64 deletions spec/rspec/matchers/built_in/be_between_spec.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,7 @@
require 'spec_helper'

describe "expect(...).to be_between(min, max)" do
it_behaves_like "an RSpec matcher", :valid_value => (10), :invalid_value => (11) do
let(:matcher) { be_between(1, 10) }
end

it "passes if target is between min and max" do
expect(10).to be_between(1, 10)
end

it "fails if target is not between min and max" do
expect {
# It does not go to 11
expect(11).to be_between(1, 10)
}.to fail_with("expected 11 to be between 1 and 10 (inclusive)")
end

it 'indicates it was not comparable if it does not respond to `between?`' do
expect {
expect(nil).to be_between(0, 10)
}.to fail_with("expected nil to be between 0 and 10 (inclusive), but it does not respond to `between?`")
end

it 'works with strings' do
expect("baz").to be_between("bar", "foo")
expect {
expect("foo").to be_between("bar", "baz")
}.to fail_with("expected \"foo\" to be between \"bar\" and \"baz\" (inclusive)")
end

it 'works with other Comparable objects' do
module RSpec::Matchers::BuiltIn
describe BeBetween do
class SizeMatters
include Comparable
attr :str
Expand All @@ -43,46 +15,145 @@ def inspect
@str
end
end
expect(SizeMatters.new("--")).to be_between(SizeMatters.new("-"), SizeMatters.new("---"))
expect {
expect(SizeMatters.new("---")).to be_between(SizeMatters.new("-"), SizeMatters.new("--"))
}.to fail_with("expected --- to be between - and -- (inclusive)")
end
end

describe "expect(...).not_to be_between(min, max)" do
it "passes if target is not between min and max" do
expect(11).not_to be_between(1, 10)
end
shared_examples_for "be_between" do |mode|
it "passes if target is between min and max" do
expect(5).to matcher(1, 10)
end

it "fails if target is between min and max" do
expect {
expect(10).not_to be_between(1, 10)
}.to fail_with("expected 10 not to be between 1 and 10 (inclusive)")
end
end
it "fails if target is not between min and max" do
expect {
# It does not go to 11
expect(11).to matcher(1, 10)
}.to fail_with("expected 11 to be between 1 and 10 (#{mode})")
end

describe "composing with other matchers" do
it "passes when the matchers both match" do
expect([nil, 2]).to include(a_value_between(2, 4), a_nil_value)
end
it "works with strings" do
expect("baz").to matcher("bar", "foo")

it 'works with mixed types (even though between? can raise ArgumentErrors)' do
expect(["baz", Math::PI]).to include( a_value_between(3.1, 3.2), a_value_between("bar", "foo") )
expect {
expect("foo").to matcher("bar", "baz")
}.to fail_with("expected \"foo\" to be between \"bar\" and \"baz\" (#{mode})")
end

expect {
expect(["baz", 2.14]).to include( a_value_between(3.1, 3.2), a_value_between("bar", "foo") )
}.to fail_with('expected ["baz", 2.14] to include (a value between 3.1 and 3.2 (inclusive)) and (a value between "bar" and "foo" (inclusive))')
end
it "works with other Comparable objects" do
expect(SizeMatters.new("--")).to matcher(SizeMatters.new("-"), SizeMatters.new("---"))

it "provides a description" do
description = include(a_value_between(2, 4), an_instance_of(Float)).description
expect(description).to eq("include (a value between 2 and 4 (inclusive)) and (an instance of Float)")
end
expect {
expect(SizeMatters.new("---")).to matcher(SizeMatters.new("-"), SizeMatters.new("--"))
}.to fail_with("expected --- to be between - and -- (#{mode})")
end
end

shared_examples_for "not_to be_between" do |mode|
it "passes if target is not between min and max" do
expect(11).not_to matcher(1, 10)
end

it "fails if target is between min and max" do
expect {
expect(5).not_to matcher(1, 10)
}.to fail_with("expected 5 not to be between 1 and 10 (#{mode})")
end
end

shared_examples_for "composing with other matchers" do |mode|
it "passes when the matchers both match" do
expect([nil, 3]).to include(matcher(2, 4), a_nil_value)
end

it "works with mixed types" do
expect(["baz", Math::PI]).to include(matcher(3.1, 3.2), matcher("bar", "foo"))

expect {
expect(["baz", 2.14]).to include(matcher(3.1, 3.2), matcher("bar", "foo") )
}.to fail_with("expected [\"baz\", 2.14] to include (a value between 3.1 and 3.2 (#{mode})) and (a value between \"bar\" and \"foo\" (#{mode}))")
end

it "fails with a clear error message when the matchers do not match" do
expect {
expect([nil, 1]).to include(a_value_between(2, 4), a_nil_value)
}.to fail_with("expected [nil, 1] to include (a value between 2 and 4 (inclusive)) and (a nil value)")
it "provides a description" do
description = include(matcher(2, 4), an_instance_of(Float)).description
expect(description).to eq("include (a value between 2 and 4 (#{mode})) and (an instance of Float)")
end

it "fails with a clear error message when the matchers do not match" do
expect {
expect([nil, 1]).to include(matcher(2, 4), a_nil_value)
}.to fail_with("expected [nil, 1] to include (a value between 2 and 4 (#{mode})) and (a nil value)")
end
end

it_behaves_like "an RSpec matcher", :valid_value => (10), :invalid_value => (11) do
let(:matcher) { be_between(1, 10) }
end

describe "expect(...).to be_between(min, max) (inclusive)" do
it_behaves_like "be_between", :inclusive do
def matcher(min, max)
be_between(min, max)
end
end

it "is inclusive" do
expect(1).to be_between(1, 10)
expect(10).to be_between(1, 10)
end

it "indicates it was not comparable if it does not respond to `<=` and `>=`" do
expect {
expect(nil).to be_between(0, 10)
}.to fail_with("expected nil to be between 0 and 10 (inclusive), but it does not respond to `<=` and `>=`")
end
end

describe "expect(...).to be_between(min, max) (exclusive)" do
it_behaves_like "be_between", :exclusive do
def matcher(min, max)
be_between(min, max).exclusive
end
end

it "indicates it was not comparable if it does not respond to `<` and `>`" do
expect {
expect(nil).to be_between(0, 10).exclusive
}.to fail_with("expected nil to be between 0 and 10 (exclusive), but it does not respond to `<` and `>`")
end

it "is exclusive" do
expect { expect(1).to be_between(1, 10).exclusive }.to fail
expect { expect(10).to be_between(1, 10).exclusive }.to fail
end
end

describe "expect(...).not_to be_between(min, max) (inclusive)" do
it_behaves_like "not_to be_between", :inclusive do
def matcher(min, max)
be_between(min, max)
end
end
end

describe "expect(...).not_to be_between(min, max) (exclusive)" do
it_behaves_like "not_to be_between", :exclusive do
def matcher(min, max)
be_between(min, max).exclusive
end
end
end

describe "composing with other matchers (inclusive)" do
it_behaves_like "composing with other matchers", :inclusive do
def matcher(min, max)
a_value_between(min, max)
end
end
end

describe "composing with other matchers (exclusive)" do
it_behaves_like "composing with other matchers", :exclusive do
def matcher(min, max)
a_value_between(min, max).exclusive
end
end
end
end
end
5 changes: 5 additions & 0 deletions spec/rspec/matchers/description_generation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
expect(RSpec::Matchers.generated_description).to eq "should be between 0 and 10 (inclusive)"
end

it "expect(...).to be exclusively between min and max" do
expect(9).to be_between(0, 10).exclusive
expect(RSpec::Matchers.generated_description).to eq "should be between 0 and 10 (exclusive)"
end

it "expect(...).to be predicate arg1, arg2 and arg3" do
class Parent; end
class Child < Parent
Expand Down

0 comments on commit fc697d0

Please sign in to comment.