[rspec-users] Should change not comparing arrays how I expected

David Chelimsky dchelimsky at gmail.com
Sun Sep 28 12:01:41 EDT 2008


On Sun, Sep 28, 2008 at 10:43 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
> On Sun, Sep 28, 2008 at 9:47 AM, Ashley Moran
> <ashley.moran at patchspace.co.uk> wrote:
>> Hi
>>
>> Just had a surprising result:
>>
>>  it "should not appear in the Story.unposted list" do
>>    @story.save
>>    lambda {
>>      @story.post_to_twitter(@twitter_client)
>>    }.should change { Story.unposted }.from([@story]).to([])
>>  end
>>
>> 'Story#post_to_twitter should not appear in the Story.unposted list' FAILED
>> result should have been changed to [], but is now []
>>
>> Anyone know why this fails?  I've looked in change.rb but I can't figure it
>> out.
>
> Whenever I've seen output like "should have been foo, but was foo" it
> has boiled down to AR Assocation Proxies, which don't align in their
> response to == and inspect.
>
> I'm looking at seeing if there's a way we can make "should change"
> work in spite.

Wow.

OK - here's what I figured out. Talk about insidious bugs! This is
actually quite a bit different from what I thought.

There are two lambdas involved here:

lambda {
  1st lambda: expression that should cause the change
}.should change{
  2nd lambda: expression that returns the object that should change
}.to(expected value)

The matcher executes the 1st lambda and stores the result as @before.
In your example this is a Rails association proxy for the
Story.unposted collection.

The matcher then executes the 2nd lambda.

The matcher then executes the 1st lambda again and stores the result
as @after. In your example, this is, again, a Rails association proxy
for the Story.unposted collection.

At this point, @before and @after point to the same object - the same
Rails association proxy!!!!!!

The matcher passes if @before != @after and fails if @before ==
@after. Because they are actually the same association proxy, the
example fails.

Now rspec asks the matcher to print out the reason why it failed,
which does this:

"#{result} should have been changed to #{@to.inspect}, but is now
#{@after.inspect}"

@to is the expected value []
@after is the association proxy, which proxies to an empty collection.
Now, when the matcher calls @after.inspect, is the first time that the
proxy is actually evaluated!!!!

Does this make sense?

I was able to get a similar example to pass by doing this immediately
after storing the proxy in the @before variable:

@before = @before.collect{|item|item} if @before.respond_to?(:collect)

Ugly, ugly, ugly. But perhaps necessary to deal w/ this problem.

I think I'll restructure things so the the change matcher handles this
in rails, but not in core rspec.

Thoughts?

>
>
>> I can make it work with:
>>  should change { Story.unposted.length }.from(1).to(0)
>>
>> But that's a weaker test.
>>
>> Thanks
>> Ashley
>>
>> --
>> http://www.patchspace.co.uk/
>> http://aviewfromafar.net/
>>
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
>>
>


More information about the rspec-users mailing list