Question: Expecting Lambdas in RSpec

Question

Expecting Lambdas in RSpec

Answers 1
Added at 2017-07-23 16:07
Tags
Question

I have a simple class:

class Maybe
  def initialize(value, maybe_klass = Maybe)
    @value = value
    @maybe_klass = maybe_klass
  end

  attr_reader :value

  def map(function)
    return self if value.nil?
    return maybe_klass.new(compose(value, function)) if value.is_a? Proc
    maybe_klass.new(function.curry.call(*value))
  end

  private

  attr_reader :maybe_klass

  def compose(f, g)
    lambda { |*args| f.call(g.call(*args))  }
  end
end

I'm trying to test it like this:

it 'creates a new wrapped function that does both of the functions combined' do
  maybe_double = double Maybe
  add_four = ->(x) { x + 4 }
  maybe = Maybe.new(add_four, maybe_double)
  composed_lambda = ->(*args) { maybe.value.call(add_four.call(*args)) }
  expect(maybe_double).to receive(:new).with(composed_lambda)
  maybe.map(add_four)
end

This test does not work- undefined method '+' for #<Proc:0x007fdd0b269e78>

I think the composed lambda is being evaluated before I need it to be, but I'm not sure. What should I expect the double's initialize method to be called with? Is this even the correct way to approach testing this class? I feel like I'm too closely mimicking the internals of the class with the composed_lambda variable, but I'm not sure what else to expect instead.

Also, because the function returns a new instance of the class we are testing, it's not really possible to stub that whole class - we'll end up trying to stub the system under test.

Answers to

Expecting Lambdas in RSpec

nr: #1 dodano: 2017-07-23 16:07

It seems, that you have correct code, but you expect rspec to work different way. Here's the issue about it

rspec uses === to verify expectations, but in ruby when you use === operator on lambdas, the lambdas are called. Thus, when you pass a lambda into receive(:new).with method, rspec calls this lambda with an argument that Maybe#new method received.

For your case, I'd suggest using this cool feature for you tests:

it 'creates a new wrapped function that does both of the functions combined' do
  maybe_double = double Maybe
  add_four = ->(x) { x + 4 }
  maybe = Maybe.new(add_four, maybe_double)
  expect(maybe_double).to receive(:new).with(lambda { |composed_lambda|
    expect(composed_lambda.(4)).to eq 12
  )
  maybe.map(add_four)
end

So, you'll verify that the lambda that Maybe#new received is a composed lambda and applies the composition on the value. That's because you can't compare two lambdas until you call them.

Hope it helps.

Source Show
◀ Wstecz