Say I have a custom matcher that checks to see if an object is a valid RGB tuple -- let's call it valid_rgb_tuple. The matcher takes an object as an argument and validates it. I'd like to be able to use that in a oneliner where the subject is expected to be a tuple. Ideally that would look/read something like this:
subject { some_method_that_should_return_a_tuple }
it { should be_a_valid_rgb_tuple }
I'm not sure how to make that work, or if it's even possible to get close. I think I need a way to force the subject to get automatically passed into the validator. Is there a construct for this?
CodePudding user response:
should be_a_valid_rgb_tuple is just expect(subject).to be_a_valid_rgb_tuple. It might seem a little less magical with parenthesis.
expect(subject()).to(be_a_valid_rgb_tuple())
expect(subject()) just saves the return value of subject in an object and returns it. to is called on that with the matcher object returned by be_a_valid_rgb_tuple and it passes the stored return value to the matcher.
Here is a very rough sketch of how rspec works.
class Expectation
def initialize(value)
@value = value
end
def to(matcher)
raise unless matcher.matches?(@value)
end
end
class ValidRgbTupleMatcher
def matches?(value)
return false unless value.is_a?(RgbTuple)
return false unless value.valid?
return true
end
end
class RgbTuple
def valid?
true
end
end
def expect(value)
Expectation.new(value)
end
def subject
RgbTuple.new
end
def be_a_valid_rgb_tuple
ValidRgbTupleMatcher.new
end
expect(subject).to be_a_valid_rgb_tuple
You'd write be_a_valid_rgb_tuple as a custom matcher and use the normal one liner syntax. Try to write up the matcher using the rspec docs and if you have trouble add a specific question.
Though you probably don't need a custom matcher. Instead, use be_a to check the class of the return value, and be_valid to check if its valid. Combine them with and.
it { should be_a(RgbTuple).and be_valid }
Equivalent to...
it {
tuple = subject
expect(tuple.is_a?(RgbTuple)).to be true
expect(tuple.valid?).to be true
}
CodePudding user response:
Matchers don't actually care about the implicit subject. Matchers just take a actual value and optionally an expected value.
require 'rspec/expectations'
RSpec::Matchers.define :be_a_multiple_of do |expected|
match do |actual|
actual % expected == 0
end
end
The implicit subject really is just a matter of how the matcher is called. If you do:
subject { 16 }
it { should be_a_multiple_of 4 }
Its just a shorthand for expect(subject).to(be_a_multiple_of(4)).
A minimal implementation of a matcher for a "RGB tuple" is:
require 'rspec/expectations'
RSpec::Matchers.define :be_a_valid_rgb_tuple do
match do |actual|
# @todo the boring work of handling bad input such as nil
# and providing better feedback
actual.select { |n| n.is_a?(Integer) && (0..255).cover?(n) }.length == 3
end
end
This assumes that by valid "RGB tuple" you mean an array with three integers between 0 and 255.
