Module: Spectus

Defined in:
lib/spectus.rb,
lib/spectus/requirement.rb,
lib/spectus/requirement/base.rb,
lib/spectus/requirement/optional.rb,
lib/spectus/requirement/required.rb,
lib/spectus/requirement/recommended.rb

Overview

A Ruby library for defining expectations with precision using RFC 2119 compliance levels.

This module provides methods to define expectations according to different requirement levels (MUST, SHOULD, MAY). Each method accepts a matcher object that responds to ‘match?` and follows the block-passing protocol.

While the Matchi gem provides a collection of ready-to-use matchers, you can create your own custom matchers:

Examples:

Creating a custom matcher

class BeTheAnswer
  def match?
    42.equal?(yield)
  end
end

test = Spectus.must BeTheAnswer.new
test.call { 42 } # => pass
test.call { 41 } # => fail

Using with Matchi gem

require "spectus"
require "matchi/eq"

test = Spectus.must Matchi::Eq.new(42)
test.call { 42 } # => pass

See Also:

Defined Under Namespace

Modules: Requirement

Class Method Summary collapse

Class Method Details

.may(matcher) ⇒ Requirement::Optional

Defines an optional feature or behavior. This represents the RFC 2119 “MAY” level - where an item is truly optional. Implementations can freely choose whether to include the item based on their specific needs, while maintaining interoperability with other implementations.

For MAY requirements, a test passes in two cases:

  1. When a NoMethodError is raised, indicating the feature is not implemented

  2. When the feature is implemented and the test succeeds

Examples:

With a custom matcher testing an optional method

class RespondsTo
  def initialize(method)
    @method = method
  end

  def match?
    (yield).respond_to?(@method)
  end
end

test = Spectus.may RespondsTo.new(:to_h)
test.call { {} }                  # => pass (feature is implemented)
test.call { BasicObject.new }     # => pass (NoMethodError - feature not implemented)

With Matchi gem

require "spectus"
require "matchi/predicate"

test = Spectus.may Matchi::Predicate.new(:be_frozen)
test.call { "".freeze }           # => pass (feature is implemented)
test.call { BasicObject.new }     # => pass (NoMethodError - feature not implemented)

Parameters:

  • matcher (#match?)

    Any object that implements the matcher protocol

Returns:

Raises:

  • (ArgumentError)

    If matcher doesn’t respond to match?



203
204
205
206
207
# File 'lib/spectus.rb', line 203

def self.may(matcher)
  raise ::ArgumentError, "matcher must respond to match?" unless matcher.respond_to?(:match?)

  Requirement::Optional.new(negate: false, matcher:)
end

.must(matcher) ⇒ Requirement::Required

Defines an absolute requirement that must be satisfied by the implementation. This represents the RFC 2119 “MUST” level - an absolute requirement of the specification.

Examples:

With a custom matcher

class PositiveNumber
  def match?
    (yield).positive?
  end
end

test = Spectus.must PositiveNumber.new
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

With Matchi gem

require "spectus"
require "matchi/eq"

test = Spectus.must Matchi::Eq.new(42)
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

Parameters:

  • matcher (#match?)

    Any object that implements the matcher protocol:

    • Responds to ‘match?`

    • Accepts a block in ‘match?` that provides the actual value

Returns:

Raises:

  • (ArgumentError)

    If matcher doesn’t respond to match?



64
65
66
67
68
# File 'lib/spectus.rb', line 64

def self.must(matcher)
  raise ::ArgumentError, "matcher must respond to match?" unless matcher.respond_to?(:match?)

  Requirement::Required.new(negate: false, matcher:)
end

.must_not(matcher) ⇒ Requirement::Required

Defines an absolute prohibition in the specification. This represents the RFC 2119 “MUST NOT” level - an absolute prohibition.

Examples:

With a custom matcher

class NegativeNumber
  def match?
    (yield).negative?
  end
end

test = Spectus.must_not NegativeNumber.new
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

With Matchi gem

require "spectus"
require "matchi/be"

test = Spectus.must_not Matchi::Be.new(0)
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

Parameters:

  • matcher (#match?)

    Any object that implements the matcher protocol

Returns:

Raises:

  • (ArgumentError)

    If matcher doesn’t respond to match?



95
96
97
98
99
# File 'lib/spectus.rb', line 95

def self.must_not(matcher)
  raise ::ArgumentError, "matcher must respond to match?" unless matcher.respond_to?(:match?)

  Requirement::Required.new(negate: true, matcher:)
end

.should(matcher) ⇒ Requirement::Recommended

Defines a recommended requirement that should be satisfied unless there are valid reasons not to. This represents the RFC 2119 “SHOULD” level - where valid reasons may exist to ignore a particular item, but the implications must be understood and weighed.

Examples:

With a custom matcher

class EvenNumber
  def match?
    (yield).even?
  end
end

test = Spectus.should EvenNumber.new
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

With Matchi gem

require "spectus"
require "matchi/be"

test = Spectus.should Matchi::Be.new(:even?)
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

Parameters:

  • matcher (#match?)

    Any object that implements the matcher protocol

Returns:

Raises:

  • (ArgumentError)

    If matcher doesn’t respond to match?



127
128
129
130
131
# File 'lib/spectus.rb', line 127

def self.should(matcher)
  raise ::ArgumentError, "matcher must respond to match?" unless matcher.respond_to?(:match?)

  Requirement::Recommended.new(negate: false, matcher:)
end

.should_not(matcher) ⇒ Requirement::Recommended

Defines a behavior that is not recommended but may be acceptable in specific circumstances. This represents the RFC 2119 “SHOULD NOT” level - where particular behavior may be acceptable but the implications should be understood and the case carefully weighed.

Examples:

With a custom matcher

class RaisesError
  def match?
    yield
    false
  rescue
    true
  end
end

test = Spectus.should_not RaisesError.new
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

With Matchi gem

require "spectus"
require "matchi/raise_exception"

test = Spectus.should_not Matchi::RaiseException.new(StandardError)
test.call { 42 }
# => #<Expresenter::Pass actual: 42, ...>

Parameters:

  • matcher (#match?)

    Any object that implements the matcher protocol

Returns:

Raises:

  • (ArgumentError)

    If matcher doesn’t respond to match?



162
163
164
165
166
# File 'lib/spectus.rb', line 162

def self.should_not(matcher)
  raise ::ArgumentError, "matcher must respond to match?" unless matcher.respond_to?(:match?)

  Requirement::Recommended.new(negate: true, matcher:)
end