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

David Chelimsky dchelimsky at gmail.com
Sun Sep 28 14:13:49 EDT 2008


On Sun, Sep 28, 2008 at 11:01 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
> 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?

FYI - ticket added and problem resolved:
http://rspec.lighthouseapp.com/projects/5645-rspec/tickets/545

>
>>
>>
>>> 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/


More information about the rspec-users mailing list