should_yield (is it needed?)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

should_yield (is it needed?)

Martin Emde
I have the code working for this but I wanted an opinion on making a patch.

I sometimes have the need to expect that a method will yield a block it's given. Usually this means something like this:

    yielded = false
    subject.method { yielded = true }
    yielded.should == true

which is a bit tedious and ugly. My solution might be just as ugly, but I wanted to get feedback because it saves that tedious code.

    subject.method(&should_yield)

I wrote a few methods, that when called, return an object with #to_proc that expects that the proc is called (or not called). I have the following forms available fairly simply:

    subject.method(&should_not_yield)
    subject.method(&should_yield_with(object))

    # Expects 3 yields, one with each argument
    [1,2,3].each(&should_yield_each(1,2,3))
 
I think this is a little cleaner and the code is very simple. I wanted to get an opinion on whether this is worth making into a patch.

Martin Emde
Tw: @martinemde

_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

Peter Jaros
I happen to think that's brilliant.

Peter


On Fri, Nov 6, 2009 at 10:43 PM, Martin Emde <[hidden email]> wrote:

> I have the code working for this but I wanted an opinion on making a patch.
> I sometimes have the need to expect that a method will yield a block it's
> given. Usually this means something like this:
>     yielded = false
>     subject.method { yielded = true }
>     yielded.should == true
> which is a bit tedious and ugly. My solution might be just as ugly, but I
> wanted to get feedback because it saves that tedious code.
>     subject.method(&should_yield)
> I wrote a few methods, that when called, return an object with #to_proc that
> expects that the proc is called (or not called). I have the following forms
> available fairly simply:
>     subject.method(&should_not_yield)
>     subject.method(&should_yield_with(object))
>     # Expects 3 yields, one with each argument
>     [1,2,3].each(&should_yield_each(1,2,3))
>
> I think this is a little cleaner and the code is very simple. I wanted to
> get an opinion on whether this is worth making into a patch.
> Martin Emde
> Tw: @martinemde
>
> _______________________________________________
> rspec-devel mailing list
> [hidden email]
> http://rubyforge.org/mailman/listinfo/rspec-devel
>
_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

David Chelimsky-2
In reply to this post by Martin Emde
On Fri, Nov 6, 2009 at 10:43 PM, Martin Emde <[hidden email]> wrote:
I have the code working for this but I wanted an opinion on making a patch.

I sometimes have the need to expect that a method will yield a block it's given. Usually this means something like this:

    yielded = false
    subject.method { yielded = true }
    yielded.should == true

which is a bit tedious and ugly. My solution might be just as ugly, but I wanted to get feedback because it saves that tedious code.

    subject.method(&should_yield)

I wrote a few methods, that when called, return an object with #to_proc that expects that the proc is called (or not called). I have the following forms available fairly simply:

    subject.method(&should_not_yield)
    subject.method(&should_yield_with(object))

    # Expects 3 yields, one with each argument
    [1,2,3].each(&should_yield_each(1,2,3))
 
I think this is a little cleaner and the code is very simple. I wanted to get an opinion on whether this is worth making into a patch.

Martin Emde
Tw: @martinemde

Generally this looks really nice, and I can see the use, but it's so different from everything else that I need to think about it a bit more. Have you played around w/ any other syntax options that would be more aligned w/ the "actual.should matcher" format? Something like:

[1,2,3].should yield_on(:each) ???
 

_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

Martin Emde
On Tue, Nov 10, 2009 at 5:00 PM, David Chelimsky <[hidden email]> wrote:

[1,2,3].should yield_on(:each) ???

This does follow rspec syntax more closely... Let's run through a few use cases I can imagine.

current_user.stub!(:admin?).and_return(true)
helper.should yield_on(:admin_only)
# helper.admin_only &should_yield

[1,2,3].should yield_on(:each).each_of(1,2,3)
# [1,2,3].each &should_yield_each(1,2,3)

h = { :a => 1, :b => 2 }
h.should yield_on(:each).each_of(*h.to_a)
h.each &should_yield_each(*h.to_a)

helper.should yield_on(:link_to_function, "link text").with(an_instance_of(JavaScriptHelper::JavaScriptBuilder))
#  helper.link_to_function "link text", &should_yield_with(an_instance_of(JavaScriptHelper::JavaScriptBuilder))

I think both are pretty clear until the last example when this part in my format:  "link text", &should_yield(...)   starts to confuse what's going on exactly. It very clearly breaks from rspec syntax in that example.

helper.link_to_functio("link text", &assert_yielded_block)

That actually makes a bit more sense since the intention is clear. I'm passing a block that asserts that it will be yielded. However, this isn't rspec at all written like that.

The yield_on format seems to specify "When I call this method on the receiver, I expect it to yield." This is very similar to user.should be_an_admin in that the actual method call is not being called on the receiver but passed into the matcher.

Is this something we'd like to add to rspec? If so, I'll work out a patch.

Martin

_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

David Chelimsky-2


On Sun, Nov 29, 2009 at 3:11 PM, Martin Emde <[hidden email]> wrote:
On Tue, Nov 10, 2009 at 5:00 PM, David Chelimsky <[hidden email]> wrote:

[1,2,3].should yield_on(:each) ???

This does follow rspec syntax more closely... Let's run through a few use cases I can imagine.

current_user.stub!(:admin?).and_return(true)
helper.should yield_on(:admin_only)
# helper.admin_only &should_yield

[1,2,3].should yield_on(:each).each_of(1,2,3)
# [1,2,3].each &should_yield_each(1,2,3)

h = { :a => 1, :b => 2 }
h.should yield_on(:each).each_of(*h.to_a)
h.each &should_yield_each(*h.to_a)

helper.should yield_on(:link_to_function, "link text").with(an_instance_of(JavaScriptHelper::JavaScriptBuilder))
#  helper.link_to_function "link text", &should_yield_with(an_instance_of(JavaScriptHelper::JavaScriptBuilder))

I think both are pretty clear until the last example when this part in my format:  "link text", &should_yield(...)   starts to confuse what's going on exactly. It very clearly breaks from rspec syntax in that example.

helper.link_to_functio("link text", &assert_yielded_block)

That actually makes a bit more sense since the intention is clear. I'm passing a block that asserts that it will be yielded. However, this isn't rspec at all written like that.

The yield_on format seems to specify "When I call this method on the receiver, I expect it to yield." This is very similar to user.should be_an_admin in that the actual method call is not being called on the receiver but passed into the matcher.

Is this something we'd like to add to rspec? If so, I'll work out a patch.

Martin

I'm a bit confused as to which syntax you're actually advocating now :)

One thing I'm realizing after your suggestion that "the actual method call is not being called on the receiver" is that this is more akin to a message expectation (i.e. mock) rather than a state-based expectation (should xxx). That leads me to think this should be part of the mock framework rather than the matcher framework, which leads me to think differently about syntax.

Also, from an implementation standpoint, I don't see how we can fail this example:

describe "foo" do
  def foo; # do nothing; end

  it "yields" do
    foo &should_yield
  end
end

In this case the proc returned by should_yield is never invoked, which means we have no way of hooking into RSpec to declare that it should be. Sort of a catch 22. We might need something like this:

foo should_yield

The should_yield() method, in this case, can set the message expectation on some sort of hidden proxy object and then return a proc that will record the expectation as satisfied when it's called. But again, this is new syntax, which I'm not sure I want to introduce for one concept.

Here's another alternative:

expect { foo }.to yield_to_block
expect { [1,2,3].each }.to yield_each_of(1,2,3)

This relates to a family of matchers that lie somewhere between state-based matchers and mocks (raise_error, change), so it's a more familiar construct.

WDYT?

David

_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

Peter Fitzgibbons
On Tue, Dec 1, 2009 at 6:55 AM, David Chelimsky <[hidden email]> wrote:
HI David and all,

This seems to me to fall into the expectations category.  We are setting expectation that our method taking &block will yield such and such somewhere inside.
I take this to be distinguished from mocking the yield, which appears to be already captured well enough in Spec::Mocks::BaseExpectation#and_yield

So, going with this belonging to Spec::Matchers, then :

subject.method(args).should yield(values,to,yield)

If the method is expected to yield multiple times, then match as David suggested :

subject.method(args).should yield_each_of(objects, yielded, in, iterations)


Unfortunately I do not have the ability yet to suggest internal implementation.  Rspec already baffles my metaprogramming-fu... the stuff we're discussing cleanly surpasses it.

wdyt?
Peter Fitzgibbons
(847) 687-7646
Email: [hidden email]
IM GTalk: peter.fitzgibbons
IM AOL: [hidden email] 


_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel
Reply | Threaded
Open this post in threaded view
|

Re: should_yield (is it needed?)

Martin Emde
In reply to this post by David Chelimsky-2
I was a bit unclear about which one I'm advocating. I can see many ways to achieve this that would be clear in different circumstances and I haven't really put my weight behind one yet.

David Chelimsky wrote:
I don't see how we can fail this example:

describe "foo" do
  def foo; # do nothing; end

  it "yields" do
    foo &should_yield
  end
end

In this case the proc returned by should_yield is never invoked, which means we have no way of hooking into RSpec to declare that it should be. Sort of a catch 22.

should_yield is first invoked with "&should_yield". The should_yield method is called and returns an object responding to #to_proc. The & calls #to_proc on our object which returns a block that calls our expected method when it's executed.

Here's my implementation. I actually think it is pretty cool, whether or not it's the "right" solution:


Now just some thoughts on syntax...

What a "should yield" expectation is really testing is an "anonymous function" call. Particularly, we're asserting that #call is called on our "block" object in some form (yield or #call). In ruby, our "anonymous functions" are blocks. The implementation and usage is then very similar to a should_receive. If I were to structure this like a should receive, I would expect something like this.

# should_receive style implementation.
block = mock_block.should_receive_yield.with(1,2,3)
[1,2,3].method("args", &block)

This actually shows that we're making a block with expectations on it, and then we're passing the block in a familiar format "&block" which most rubyists will recognize. It could even be shortened by people who understand what they're doing to something like this:

[1,2,3].method("args", &mock_block.should_receive_yield.with(1,2,3))

This opens up a really comfortable feeling language where you can even have return values from the block in a way that makes sense.

# this block doubles the number passed in
block = mock_block.should_receive_yield.with(1,2,3).and_return(2,4,6)
[1,2,3].map(&block).should == [2,4,6]

My only concern for the & syntax is just how confusing it is to most people. This is really just stepping back a bit to expose my implementation a bit more. In my implementation the "mock_block.should_recieve_yield" is just "should_yield" which does the mock_block part behind the scenes. Thoughts?

Martin

P.S. Peter, your implementation reads well but the method(args) will call the method right away without a block. That's the catch here that makes this a tricky thing to spec.

_______________________________________________
rspec-devel mailing list
[hidden email]
http://rubyforge.org/mailman/listinfo/rspec-devel