crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Spec expectation method to match similar collections

Open jwoertink opened this issue 3 years ago • 3 comments

I find myself doing something like this a lot in my specs, and figured it might be a cool addition.

results = [1, 2, 3, 4]
results.should contain(2)
results.should contain(4)
results.should_not contain(5)
results.should_not contain(0)

Maybe the contain could take a splat? or we could have a new method to handle this?

results.should contain(2, 4)
results.should_not contain(5, 0)

results.should have([2, 4])
results.should_not have([5, 0])

Something like this could probably also work for hashes

results = {"id" => "1234", "key" => "sdfhu3", "code" => "xxxx"}

results.should have({"key" => "sdfhu3", "code" => "xxxx"})
results.should_not have({"pass" => "@", "item" => "G12"})

I believe Rspec supports this though, I'm sure it's probably done with some Ruby meta magic :zany_face: lol

jwoertink avatar Aug 04 '22 17:08 jwoertink

Adding a splat arg to contain looks easy enough. It won't support dynamic collections, but that's probably fine for the spec use case.

On the other hand, I'm not sure how useful that really is... In practice, for positive expectations I'd rather be more specific about the exact collection (or sub-collection if it's to much burdon to cover the whole) for most typical use cases.

results = [1, 2, 3, 4]
results.should eq [1, 2, 3, 4]
# or
results[1, 3].should eq [2, 3, 4]
# or
results[1].should eq(2)
results[3].should eq(4)

So I'd be interested in concrete use cases for a collection inclusion expectation.

straight-shoota avatar Aug 04 '22 22:08 straight-shoota

So I'd be interested in concrete use cases for a collection inclusion expectation.

Sure.

it "returns a list of my friends, with some random users added in" do
  # lots of setup stuff
  results = graph_query.fetch_user_list(current_user)
  
  usernames = results.map(&.username)
  # I only care that these 3 are in the list, but the list has more than 3 
  (usernames.size > 3).should be_true
  usernames.should contain("friend1")
  usernames.should contain("friend2")
  usernames.should contain("friend3")
end

It would be kinda cool if I could just do

usernames.should contain("friend1", "friend2", "friend3")

jwoertink avatar Aug 04 '22 23:08 jwoertink

I think users should be able to write their own expectation classes for the standard library's Spec. (I did this once for the compiler's own specs.) The expectation object's interface is apparently not documented yet. Here is an example:

require "spec"

private record ContainManyExpectation(*T), values : T do
  def match(actual_value) : Bool
    @values.all? { |value| actual_value.includes?(value) }
  end

  # failure message used by `should`
  def failure_message(actual_value) : String
    "Expected: ..."
  end

  # failure message used by `should_not`
  def negative_failure_message(actual_value) : String
    "Expected: ... not to ..."
  end
end

def contain_many(*values : *T) forall T
  ContainManyExpectation(*T).new(values)
end

it { (6..).should contain_many(5, 7, 1000) } # should fail

HertzDevil avatar Aug 06 '22 18:08 HertzDevil