Tomas Pospisek
2007-Jan-11 11:54 UTC
[mocha-developer] counter-intuitive behaveour when passing a proc to Mocha::Expectation#returns
Let''s say that I have a procedure that: * gets the number of bytes to write into some file * and returns the number of successfully written bytes def save( text, len ) This procedure is being used all over the place especially inside the method "process" which I want to test. Thus in order not to polute my filesystem I stub the "save" procedure. But of course in order for my "process" method I need the "save" procedure to return the correct number of bytes. Thus naively I''d do something like this: stubs( :save ). returns( lambda { eval "len" } ) Unfortunately this doesn''t work, since the "stubs" method doesn''t at all take parameter names into account. As far as I can see, this is the place where the actual method replacement is being done: def define_new_method stubbee.metaclass.class_eval "def #{method}(*args, &block) mocha.method_missing(:#{method}, *args, &block); end" end and it just passes all the arguments as an array. I''m not suggesting the fix for the problem at hand is trivial - I can''t see at a glance how one can determine the signature of some method wrt to its parameter*names*, however from the Mocha-user''s aka tester''s perspective Mocha''s behaveour is quite confusing. *t, exploring Mocha ---------------------------------------------------------------- This message was sent using IMP, the Internet Messaging Program.
James Mead
2007-Jan-11 12:36 UTC
[mocha-developer] counter-intuitive behaveour when passing a proc to Mocha::Expectation#returns
On 11/01/07, Tomas Pospisek <tpo2 at sourcepole.ch> wrote:> > Let''s say that I have a procedure that: > > * gets the number of bytes to write into some file > * and returns the number of successfully written bytes > > def save( text, len ) > > This procedure is being used all over the place especially inside the > method > "process" which I want to test. Thus in order not to polute my filesystem > I stub > the "save" procedure. But of course in order for my "process" method I > need the > "save" procedure to return the correct number of bytes. Thus naively I''d > do > something like this: > > stubs( :save ). returns( lambda { eval "len" } ) > > Unfortunately this doesn''t work, since the "stubs" method doesn''t at all > take > parameter names into account. As far as I can see, this is the place where > the > actual method replacement is being done: > > def define_new_method > stubbee.metaclass.class_eval "def #{method}(*args, &block) > mocha.method_missing(:#{method}, *args, &block); end" > end > > and it just passes all the arguments as an array. I''m not suggesting the > fix for > the problem at hand is trivial - I can''t see at a glance how one can > determine > the signature of some method wrt to its parameter*names*, however from the > Mocha-user''s aka tester''s perspective Mocha''s behaveour is quite > confusing. > > *t, exploring MochaCurrently Mocha does not allow the Proc in returns() to have access to the parameters passed into the stubbed method. Although it would be technically possible, I don''t think this is a good idea as it would allow you to add behaviour to the stub. Stubs are all about canned responses (see http://www.martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs). What you are talking about sounds more like a Fake Object. If what you want to do is stub the ''save'' method for your tests so that it appears that files always get saved successfully, but nothing gets written to the file-system - I''d be more inclined to define a fake version of the class which have the desired implementation. This is what the ''test/mocks'' directory allows you to do in Rails. Alternatively you might be able to use dependency injection to inject the fake version from your tests. If you want a more detailed response, please post an example test and the code under test. -- James. http://blog.floehopper.org
Tomas Pospisek
2007-Jan-11 14:23 UTC
[mocha-developer] counter-intuitive behaveour when passing a proc to Mocha::Expectation#returns
Quoting James Mead <jamesmead44 at gmail.com>:> On 11/01/07, Tomas Pospisek <tpo2 at sourcepole.ch> wrote: > > > > Let''s say that I have a procedure that: > > > > * gets the number of bytes to write into some file > > * and returns the number of successfully written bytes > > > > def save( text, len ) > > > > This procedure is being used all over the place especially inside the > > method > > "process" which I want to test. Thus in order not to polute my filesystem > > I stub > > the "save" procedure. But of course in order for my "process" method I > > need the > > "save" procedure to return the correct number of bytes. Thus naively I''d > > do > > something like this: > > > > stubs( :save ). returns( lambda { eval "len" } ) > > > > Unfortunately this doesn''t work, since the "stubs" method doesn''t at all > > take > > parameter names into account. As far as I can see, this is the place where > > the > > actual method replacement is being done: > > > > def define_new_method > > stubbee.metaclass.class_eval "def #{method}(*args, &block) > > mocha.method_missing(:#{method}, *args, &block); end" > > end > > > > and it just passes all the arguments as an array. I''m not suggesting the > > fix for > > the problem at hand is trivial - I can''t see at a glance how one can > > determine > > the signature of some method wrt to its parameter*names*, however from the > > Mocha-user''s aka tester''s perspective Mocha''s behaveour is quite > > confusing. > > > > *t, exploring Mocha > > > Currently Mocha does not allow the Proc in returns() to have access to the > parameters passed into the stubbed method. Although it would be technically > possible, I don''t think this is a good idea as it would allow you to add > behaviour to the stub. Stubs are all about canned responses (see >http://www.martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs). The example given for a stub in the referred essay is this: public class MailServiceStub implements MailService { private List<Message> messages = new ArrayList<Message>(); public void send (Message msg) { messages.add(msg); } public int numberSent() { return messages.size(); } } Mocha won''t allow you to do this, since you''d need to have access to "Message msg" when stubbing the "send" method above, which isn''t the case as you note. Thus we are not able to save the message for further verification - f.ex. if the calling unit, which we want to test is actually passing the message on to the MailServiceStub or whether it''s eating it. Now arguably that already crosses the line to a mock, since what the above does in this case is observe behaveour, as defined by Martin Fowler in the above article. Which raises the question, whether there''s really a marked and meaningfull difference between the concepts or whether it''s "only" the original author that doesn''t have an absolutely tight grasp on where the frontier between stubs and mocks (and fake objects) really lies?> What you are talking about sounds more like a Fake Object. > > If what you want to do is stub the ''save'' method for your tests so that it > appears that files always get saved successfully, but nothing gets written > to the file-system - I''d be more inclined to define a fake version of the > class which have the desired implementation. This is what the ''test/mocks'' > directory allows you to do in Rails. Alternatively you might be able to use > dependency injection to inject the fake version from your tests. > > If you want a more detailed response, please post an example test and the > code under test.I''m fine with your response, thanks a lot. I do however suggest to either document the fact that stubs don''t have access to parameters or uuuugh ... to enable (implement) the access ;-) ... Actually, not being able to access the parameters led me to rethink and rewrite the test and implement it more cleanly by using mock = stub( :method ) mock.with() { |method_params, ...| check( method_params ) } so the side-effect of stubs not allowing parameter access is actually positive... ;-) Thanks again, *t ---------------------------------------------------------------- This message was sent using IMP, the Internet Messaging Program.
Chris Roos
2007-Jan-11 15:16 UTC
[mocha-developer] counter-intuitive behaveour when passing a proc to Mocha::Expectation#returns
<snip>> > The example given for a stub in the referred essay is this: > > public class MailServiceStub implements MailService { > private List<Message> messages = new ArrayList<Message>(); > public void send (Message msg) { > messages.add(msg); > } > public int numberSent() { > return messages.size(); > } > } > > Mocha won''t allow you to do this, since you''d need to have access to "Message > msg" when stubbing the "send" method above, which isn''t the case as you note.Maybe not, but ruby will :-) The equivalent would be, as James previously suggested, a fake version. Somewhere in the load path of your tests, you would define the above class. Something like: class MailServiceStub def initialize(messages = []) @messages = messages end def send(msg) @messages.add(msg) end def number_sent @messages.size end end> > Thus we are not able to save the message for further verification - f.ex. if the > calling unit, which we want to test is actually passing the message on to the > MailServiceStub or whether it''s eating it. >Actually, you don''t need the MailServiceStub in order to test whether your calling unit is passing the message on. You would probably have something like: def test_should_pass_message_to_mail_service message = stub mail_service = mock mail_service.expects(:send).with(message) client = Client.new client.send_message(mail_service, message) end A simple Client to satisfy this test would be: class Client def send_message(mail_service, message) mail_service.send(message) end end To be honest, I''m slightly confused by your two messages but am hoping this explanation helps a bit. Chris
Tomas Pospisek
2007-Jan-11 16:39 UTC
[mocha-developer] counter-intuitive behaveour when passing a proc to Mocha::Expectation#returns
Quoting Chris Roos <chrisjroos at gmail.com>:> <snip> > > > > The example given for a stub in the referred essay is this: > > > > public class MailServiceStub implements MailService { > > private List<Message> messages = new ArrayList<Message>(); > > public void send (Message msg) { > > messages.add(msg); > > } > > public int numberSent() { > > return messages.size(); > > } > > } > > > > Mocha won''t allow you to do this, since you''d need to have access to > "Message > > msg" when stubbing the "send" method above, which isn''t the case as you > note. > > Maybe not, but ruby will :-) The equivalent would be, as James > previously suggested, a fake version. Somewhere in the load path of > your tests, you would define the above class. Something like: > > class MailServiceStub > def initialize(messages = []) > @messages = messages > end > def send(msg) > @messages.add(msg) > end > def number_sent > @messages.size > end > endYup, that''s how I was previously doing it, i.e. all mocking/stubbing/faking etc programmed by hand. However, since I don''t much use (or like - it makes me not understand my own code any more after a while) dependency injection, and the method I want to stub ahhhhhh aka mock is instantiated somewhere deep inside my class/object hierarchy, I had to sneak through all those layers to define it there. Which was very ugly and error prone, since I would forget to redefine my "stubbed" method back to its original in some test, making another completely unrelated test way down the chain fail for no trivially apparent reason. That''s why I much, much prefer using Mocha. It makes my life *way* easier, since it''s cleanly and properly done... :-)))> > Thus we are not able to save the message for further verification - f.ex. > if the > > calling unit, which we want to test is actually passing the message on to > the > > MailServiceStub or whether it''s eating it. > > > Actually, you don''t need the MailServiceStub in order to test whether > your calling unit is passing the message on. You would probably have > something like: > > def test_should_pass_message_to_mail_service > message = stub > mail_service = mock > mail_service.expects(:send).with(message) > client = Client.new > client.send_message(mail_service, message) > end > > A simple Client to satisfy this test would be: > > class Client > def send_message(mail_service, message) > mail_service.send(message) > end > end > > To be honest, I''m slightly confused by your two messages but am hoping > this explanation helps a bit.It''s actually how I finally did it - by using with... Thanks, *t ---------------------------------------------------------------- This message was sent using IMP, the Internet Messaging Program.