David,

It seems to me that the root of the problem is that the specification is incorrect. Since Rails returns association proxies the specification fails because it does not specify what the behavior should be. I would suggest that instead of patching the change matcher, that you should add a change_contents matcher that matches the contents of a collection vs. the contents of a collection. That way the framework is not guessing what was meant, but relying on the specification to be correct. Since that is really what you want to specify (the contents have changed). I think this is cleaner.

Michael


On Sep 28, 2008, at 11:13 AM, David Chelimsky wrote:

On Sun, Sep 28, 2008 at 11:01 AM, David Chelimsky <[EMAIL PROTECTED] > wrote:
On Sun, Sep 28, 2008 at 10:43 AM, David Chelimsky <[EMAIL PROTECTED] > wrote:
On Sun, Sep 28, 2008 at 9:47 AM, Ashley Moran
<[EMAIL PROTECTED]> 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([EMAIL PROTECTED]).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 [EMAIL PROTECTED], but is now
[EMAIL PROTECTED]"

@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/
_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to