Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RSpec::Matchers::BuiltIn::BeBetween #405

Merged
merged 6 commits into from
Jan 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
script: "script/test_all"
before_install:
- gem update bundler
- bundle --version
- gem update --system 2.1.11
- gem --version
bundler_args: "--standalone --binstubs --without documentation"
script: "script/test_all"
rvm:
- 1.8.7
- 1.9.2
Expand All @@ -8,10 +13,13 @@ rvm:
- 2.1.0
- jruby-18mode
- jruby-19mode
- ree
- jruby-head
- rbx
before_install:
- gem update bundler
- bundle --version
- gem update --system 2.1.11
- gem --version
- ree
- ruby-head
matrix:
allow_failures:
- rvm: jruby-head
- rvm: ruby-head
fast_finish: true

15 changes: 15 additions & 0 deletions lib/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,21 @@ def be_a_kind_of(expected)
alias_method :be_kind_of, :be_a_kind_of
alias_matcher :a_kind_of, :be_a_kind_of

# Passes if actual.between?(min, max). Works with any Comparable object,
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
# Float, Complex, and Rational).
#
# @note Inclusive of both min and max values.
#
# @example
#
# expect(5).to be_between(1, 10)
# expect(11).not_to be_between(1, 10)
def be_between(min, max)
BuiltIn::BeBetween.new(min, max)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a matcher alias for this:

alias_matcher :a_value_between, :be_between

...which allows the matcher to read correctly in composed matcher expressions:

expect(ages).to include( a_value_between(10, 20) )

It also makes the failure message read better. You can add a spec to spec/rspec/matchers/aliases_spec.rb.

alias_matcher :a_value_between, :be_between

# Passes if actual == expected +/- delta
#
# @example
Expand Down
11 changes: 5 additions & 6 deletions lib/rspec/matchers/built_in.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
module RSpec
module Matchers
module BuiltIn
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
autoload :BeBetween, 'rspec/matchers/built_in/be_between'
autoload :Be, 'rspec/matchers/built_in/be'
autoload :BeTruthy, 'rspec/matchers/built_in/be'
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
autoload :BeFalsey, 'rspec/matchers/built_in/be'
autoload :BeNil, 'rspec/matchers/built_in/be'
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
autoload :BePredicate, 'rspec/matchers/built_in/be'
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
autoload :BeTruthy, 'rspec/matchers/built_in/be'
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
autoload :Change, 'rspec/matchers/built_in/change'
autoload :Compound, 'rspec/matchers/built_in/compound'
Expand All @@ -33,11 +34,9 @@ module BuiltIn
autoload :StartWith, 'rspec/matchers/built_in/start_and_end_with'
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
autoload :YieldControl, 'rspec/matchers/built_in/yield'
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for organizing these -- I thought they were already in alphabetical order but I guess not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the alphabetization into a separate commit, to keep things clean.

autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
end
end
end


40 changes: 40 additions & 0 deletions lib/rspec/matchers/built_in/be_between.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module RSpec
module Matchers
module BuiltIn
class BeBetween < BaseMatcher
include Composable

def initialize(min, max)
@min, @max = min, max
end

def matches?(actual)
@actual = actual
comparable? and @actual.between?(@min, @max)
end

def failure_message
"expected #{@actual.inspect} to #{description}#{not_comparable_clause}"
end

def failure_message_when_negated
"expected #{@actual.inspect} not to #{description}"
end

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

private

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

def not_comparable_clause
", but #{@actual.inspect} does not respond to `between?`" unless comparable?
end
end
end
end
end
5 changes: 2 additions & 3 deletions lib/rspec/matchers/built_in/be_within.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def failure_message
end

def failure_message_when_negated
"expected #{@actual} not to #{description}"
"expected #{@actual.inspect} not to #{description}"
end

def description
Expand All @@ -51,8 +51,7 @@ def needs_expected
end

def not_numeric_clause
return "" if numeric?
", but it could not be treated as a numeric value"
", but it could not be treated as a numeric value" unless numeric?
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions spec/rspec/matchers/aliases_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ module RSpec
).with_description("a kind of Integer")
end

specify do
expect(
a_value_between(1, 10)
).to be_aliased_to(
be_between(1, 10)
).with_description("a value between 1 and 10 (inclusive)")
end

specify do
expect(
a_value_within(0.1).of(3)
Expand Down
74 changes: 74 additions & 0 deletions spec/rspec/matchers/built_in/be_between_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me laugh :).

expect(11).to be_between(1, 10)
}.to fail_with("expected 11 to be between 1 and 10 (inclusive)")
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
class SizeMatters
include Comparable
attr :str
def <=>(other)
str.size <=> other.str.size
end
def initialize(str)
@str = str
end
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

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

describe "composing with other matchers" do
it "passes when the matchers both match" do
expect([0.1, 2]).to include(a_value_between(2, 4), an_instance_of(Float))
end

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

it "fails with a clear error message when the matchers do not match" do
expect {
expect([0.1, 1]).to include(a_value_between(2, 4), an_instance_of(Float))
}.to fail_with("expected [0.1, 1] to include (a value between 2 and 4 (inclusive)) and (an instance of Float)")
end
end
15 changes: 13 additions & 2 deletions spec/rspec/matchers/description_generation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,20 @@
expect(RSpec::Matchers.generated_description).to eq "should be > 3"
end

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

it "expect(...).to be predicate arg1, arg2 and arg3" do
expect(5.0).to be_between(0,10)
expect(RSpec::Matchers.generated_description).to eq "should be between 0 and 10"
class Parent; end
class Child < Parent
def child_of?(*parents)
parents.all? { |parent| self.is_a?(parent) }
end
end
expect(Child.new).to be_a_child_of(Parent, Object)
expect(RSpec::Matchers.generated_description).to eq "should be a child of Parent and Object"
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to keep an example for the generated description of be_between -- you mind adding that back but with a doc string stating it's for be_between rather then general predicates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totes.


it "expect(...).to equal" do
Expand Down