[rspec-devel] [ rspec-Feature Requests-5064 ] Let mock() take a class argument

noreply at rubyforge.org noreply at rubyforge.org
Sun Nov 18 20:28:11 EST 2007


Feature Requests item #5064, was opened at 2006-07-14 21:28
You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=5064&group_id=797

Category: None
Group: None
>Status: Closed
Priority: 3
Submitted By: Aslak Hellesøy (aslak_hellesoy)
Assigned to: Nobody (None)
Summary: Let mock() take a class argument

Initial Comment:
I'd like to start my development by using:
  thing = mock("thing")

Once I have discovered thing's interface, I'd like to switch to:
  thing = mock(Thing)

The idea is that should_expect would fail as long as Thing doesn't implement the expected method.
This is a great way to go to the next step - implementing the Thing interface.



----------------------------------------------------------------------

Comment By: Chad Humphries (spicycode)
Date: 2007-11-19 01:22

Message:
Moved to http://rspec.lighthouseapp.com/projects/5645-rspec/tickets/18-5064-let-mock-take-a-class-argument#ticket-18-2

----------------------------------------------------------------------

Comment By: Pat Maddox (pergesu)
Date: 2006-11-30 04:13

Message:
ooh!  I just had a decent (I think) idea.

Instead of passing in a class, we can check to see if a class implement's the interface we've discovered on a particular mock.  For example

... specs

specify "Thing should implement foo, bar, baz" do
  Thing.should_implement(@mock_thing)
end

should_implement would be really simple.  Just loop through all the mocked/stubbed methods on @mock_thing and see if Thing responds to each one.

If Thing doesn't conform to the interface, the output is something like
'Thing should implement foo, bar, baz' FAILED
Thing doesn't implement
  - foo
  - baz

Since Ruby is dynamically typed, we lose a little bit (in some cases, perhaps such as this) where we don't have a defined interface.  But I think it's dangerous to let the spec code get coupled to concrete collaborator classes.  We can take advantage of Ruby here by verifying that a class satisfies the same interface as a mock we're using.

----------------------------------------------------------------------

Comment By: Pat Maddox (pergesu)
Date: 2006-11-30 04:06

Message:
As I mentioned earlier, the problem with this is that the specs will now be coupled to a concrete class.  That is a bad direction for us to take RSpec, considering that it's (imo) the best tool for driving development.

What about instead of passing in a class, we just have a command line switch that shows each mock's interface?  rspec --show-interfaces thing_spec.rb produces:
Thing
 - foo
 - bar
 - baz

I know that's not quite what you want, but it will easily let you see if your class conforms to the interface.

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2006-11-30 00:15

Message:
There are a lot of different ideas expressed in this thread. The thing that would be the most simple and, in my view, the most useful, would be Aslak's initial request BUT instead of failing you'd get feedback. We wouldn't use this for integration at all. So, if you do this:

thing = mock('thing')

you get things as they are now. If you do this:

thing = mock(Thing)

you get things as they are now, w/ failure messages reading 'Thing'. Then, if you CHOOSE to, you can run the spec command w/ a switch that simply provides a report of methods that are being called on the mock Thing that Thing will not respond to.

This can be very helpful when doing a top-down style of development in which mocks are used to help you discover the interfaces of the collaborators. In a statically typed language like java, you get this benefit automatically because you have to add methods to a java interface as you discover them. This facility in rspec would give you that little extra edge.

The whole doubling as integration idea is probably silly.

Cheers,
David

----------------------------------------------------------------------

Comment By: Pat Maddox (pergesu)
Date: 2006-11-29 23:27

Message:
I've been thinking about this a lot over the past couple days.  We use rspec and mocks to specify the behavior of and interaction between objects.  When we mock behavior, all we care about is the interface - concrete classes shouldn't matter at all.

The more I think about it the less I like it.  Things like integration mode test behavior of the system, rather than of the objects themselves, and I think that's something that should be separate from the standard specifications.  Whether that's something that should be a part of RSpec, I'm not sure...but I am certain that testing the system behavior should be separate from specifying object behavior and interactions.

----------------------------------------------------------------------

Comment By: Nathan Sobo (nathansobo)
Date: 2006-11-29 22:22

Message:
It would be helpful to continue to allow the mock method to take an optional descriptive string argument after the class argument.

Also, the should_receive method could be extended with an optional modifier that would suppress warnings for messages not corresponding to methods in the mocked class, such as messages that are known to be handled by method_missing. It could look like...

@mock.should_receive(:foo).and_not_warn
@mock.should_receive(:foo).not_in_interface

or something.

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2006-10-12 11:25

Message:
I've been thinking about this a bit and I'd like to take it even further. The thing that bugs me about this idea is that I have to keep track of which mocks are using a name and which are using the Class.

What if we started creating an empty Thing class and wrote this:

thing = mock(Thing)

That would run if the class exists, but it not yet be defined in any detail.

Then we could run specs in varying modes. Normally, they would run as they do now, with the exception that the mock expectation would read "Mock <Thing> expected...." or something like that.

A second mode, invoked w/ a command line option, would be that it does the checking that you talk about to make sure that all the messages being mocked have corresponding methods on the real class.

A third mode would be something someone proposed on the email list - an integration mode. This would require some additional setup and I'm not sure what that looks like (perhaps an integration_setup block?), but the idea is that you could run all of the same specs using real objects instead of the mocks. You wouldn't have to write separate integration specs.

This is something Dan described about JBehave when he and I spoke at Agile 06. When you run JBehave, it tells you how many mocks are still being used. The idea there is that you should remove them over time - just use them to get you there, but use the real objects as they come to be. When there are no mocks in the run and all the specs pass, you're done!

The nice thing about having a CL option to run them this way is that you can monitor your progress towards "done", but you can also run the specs in isolation normally. This gives you the benefits of both worlds - isolation and integration. In fact the mock modes could be isolation (default), verification and integration. That has some nice symmetry to it.

Thoughts?

David

----------------------------------------------------------------------

You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=5064&group_id=797


More information about the rspec-devel mailing list