Home > Mobile >  Is there a concept of an implicit subject to be passed to an rspec matcher?
Is there a concept of an implicit subject to be passed to an rspec matcher?

Time:01-23

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.

  •  Tags:  
  • Related