Skip to content

Replace most matchers by obj.should.foo #1350

Description

@eregon

Looking at this list we'd remove (or maybe deprecate first? but seems unnecessary) in MSpec and migrate usages in ruby/spec for:

be_an_instance_of(C) -> .should.instance_of?(C)
be_ancestor_of -> .ancestors.should.include?
be_empty -> .should.empty?
be_false -> .should == false
be_kind_of(C) -> .should.is_a?(C) or .should.kind_of?(C)
be_nan -> .should.nan?
be_nil -> .should == nil
be_true -> .should == true
eql -> .should.eql?
equal -> .should.equal?
have_class_variable -> .class_variables(false).should.include? BTW the matcher used inherit=true, a bug
have_constant -> .constants(false).should.include? BTW the matcher used inherit=true, a bug
have_instance_method -> .instance_methods(false).should.include? BTW the matcher used inherit=true, a bug
have_instance_variable -> .instance_variables.should.include?
have_method -> .methods(false).should.include? BTW the matcher used inherit=true, a bug
have_private_instance_method -> .private_instance_methods(false).should.include? BTW the matcher used inherit=true, a bug
have_private_method -> .private_methods(false).should.include? BTW the matcher used inherit=true, a bug
have_protected_instance_method -> .protected_instance_methods(false).should.include? BTW the matcher used inherit=true, a bug
have_public_instance_method -> .public_instance_methods(false).should.include? BTW the matcher used inherit=true, a bug
have_singleton_method -> .singleton_methods(false).should.include? BTW the matcher used inherit=true, a bug
include -> .should.include?
be_positive_infinity -> .should.infinite? == 1
be_negative_infinity -> .should.infinite? == -1
respond_to -> .should.respond_to?

We'd keep (because they cannot be expressed easily as .should.foo):

be_close
be_computed_by
be_true_or_false # maybe we should have something like `be_either(true, false)`/`be_one_of(true, false)`
block_caller
complain
include_any_of # 0 usages in ruby/spec, only used in spec/truffle/interop, probably could be replaced or moved there and then removed
match_yaml # only used in library/yaml should be moved there
output_to_fd
output
raise_error # though I would love to find a more consistent syntax, maybe `-> { ... }.should.raise(ArgumentError, "message")` since it wouldn't make sense to do `.should.raise` before and BTW Kernel#raise is a private method.
be_positive_zero # 0.0.equal? 0.0 works but relies on Flonum
be_negative_zero # (-0.0).equal?(-0.0) doesn't work on CRuby (but does on TruffleRuby). Too bad there isn't Float#signum or so
skip

Others:

equal_element -> should move to library/cgi/spec_helper.rb or so since it's only used there

The reasoning is those matchers to be removed do nothing more than just .should.foo and it's far clearer to have the method call literally in the code vs having to "deserialize RSpec notation". See this discussion and this blog post for more thoughts on this.
Right now we use a mix of both but it'd be good to be consistent.

Activity

  1. trinistr commented on Mar 6, 2026

    @trinistr
    Contributor

    Good idea!

    Some thoughts:

    1. Having a small, limited list of matchers would make it possible to just list them all in CONTRIBUTING.md here. Currently it's necessary to look at MSpec's source to learn about rarer matchers.
    2. Reading your blog post made me appreciate should.method syntax more. I think linking to it in docs would be good. And just generally point out that it's the recommended method to test stuff, instead of looking for specific matchers.
    3. Moving highly specifc matchers into the libraries's specs would be good. match_yaml is another one that's only used in library/yaml.

    be_one_of(true, false)

    I think there is potential in be_one_of(*values) matcher. Even if it's rare to need it, it could be used, for example, for IO::Buffer::HOST_ENDIAN.should be_one_of(IO::Buffer::BIG_ENDIAN, IO::Buffer::LITTLE_ENDIAN).
    Though this relation this can easily be expressed with [a, b].should.include?(value), so maybe there is no reason to actually have this or be_true_or_false?

  2. eregon commented on Mar 6, 2026

    @eregon
    MemberAuthor

    Re 1 & 2, yes, we'll need to update CONTRIBUTING.md to reflect this and why not link to the blog post.

    match_yaml

    good point, updated

    Though this relation this can easily be expressed with [a, b].should.include?(value), so maybe there is no reason to actually have this or be_true_or_false?

    Indeed and that's used in some places, but it's not ideal because it reverses the order (the expected value should be on the right and not the left).
    So yeah I think we should add be_one_of or similar with a better name.
    Maybe be_in(a, b) or be_amongst(a, b) or be_one_of(a, b).
    Python has 3 in [1,3] # => True but not sure what's a good way to express that in Ruby. I think there was a proposal once to have Object#in? or so. EDIT: https://bugs.ruby-lang.org/issues/3845

  3. self-assigned this
    on May 7, 2026
  4. eregon commented on May 7, 2026

    @eregon
    MemberAuthor

    This is basically all done now, all usages of matchers to be migrated are migrated (though they don't warn for deprecation yet), I added .should.raise and updated CONTRIBUTING.md.
    What's left is:

    • Deprecate old matchers: ruby/mspec@82868a2
    • Cleanups related to the matchers we keep
    • Add be_in or so
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions