how to ensure signature compliance while mocking in ruby

P

Pradeep Gatram

Let me put my dilemma as an example. Take a look at a snippet from
FooTest.

#using mocha
def test_method1
Bar.expects:)method2).with('param1', 'param2').once
Foo.method1
end

And now the implementation

class Foo
def self.method1
Bar.method2('param1', 'param2')
end
end

class Bar
end

So far so good... Now my dilemma is that while implementing method2 in
Bar, I can come up with any weird signature (e.g. def method2
someInteger, someOtherNumber) and I will not be breaking FooTest. This
obviously makes life hard while refactoring. Coming from a Java/C#
background, I used to rely on compilation to catch these issues.

How have people solved such problems?

Pradeep
 
J

James Mead

Note: parts of this message were removed by the gateway to make it a legal Usenet post.

Let me put my dilemma as an example. Take a look at a snippet from
FooTest.

#using mocha
def test_method1
Bar.expects:)method2).with('param1', 'param2').once
Foo.method1
end

And now the implementation

class Foo
def self.method1
Bar.method2('param1', 'param2')
end
end

class Bar
end

So far so good... Now my dilemma is that while implementing method2 in
Bar, I can come up with any weird signature (e.g. def method2
someInteger, someOtherNumber) and I will not be breaking FooTest. This
obviously makes life hard while refactoring. Coming from a Java/C#
background, I used to rely on compilation to catch these issues.

How have people solved such problems?

Pradeep


Hi Pradeep,
Fundamentally you should never rely on only mock based tests, you should
always have some functional tests to check all the objects are wired
together correctly, but...

Mocha [1] used to only allow you to mock *existing* methods on *concrete*
classes. This functionality accidentally got removed a while ago, but I'd
like to reintroduce it asap.

You can already restrict expecations on *mocks* by using the responds_like
modifier [2], something like this...

class Foo
def bar
end
end

def test_me
foo = mock('foo')
foo.responds_like(Foo.new)
foo.expects:)not_bar)
foo.not_bar
end

# => NoMethodError: undefined method `not_bar' for #<Mock:foo> which
responds like #<Foo:0x432e5c>

--
James.
http://blog.floehopper.org
http://tumble.floehopper.org

[1] http://mocha.rubyforge.org
[2] http://mocha.rubyforge.org/classes/Mocha/Mock.html#M000032
 
D

David Chelimsky

Mocha [1] used to only allow you to mock *existing* methods on *concrete*
classes. This functionality accidentally got removed a while ago, but I'd
like to reintroduce it asap.

I beg of you, please don't. At least not as a default behaviour.

Mocks are very powerful tools for interface discovery
(http://www.jmock.org/oopsla2004.pdf). With an enforcement rule like
the one you propose reinstating, we'd have to stop working on the
object at hand to go write a class and/or method. This would break the
flow of the current task, force us to shift focus.

Not only do we break the current flow, but by going over to the other
object and sticking in a stub to get the mock to shut up, we run a far
greater risk of leaving things 1/2 done than we do by sending
unsupported messages and have our integration tests expose those
holes.

For anybody who is serious about doing TDD, this would be a major step
backwards.

What we've talked about adding to ... ahem ... another mocking
library, is the ability engage this behaviour explicitly with an
environment variable or a command line switch. That would provide the
best of both worlds because you could stay focused on the task at hand
AND you could get a report of the methods you don't have on
collaborating classes so you know where to go next.

I'd strongly recommend that you consider a similar path before simply
forcing this rule on mocha users.

Cheers,
David
 
P

Pradeep Gatram

Hi James,

Sorry for vanishing for a couple of weeks. I have been using ur
suggested approach of 'responds_like'. Although its a good start, it
still does not give me complete confidence. All it tells me is whether
the method is present or not. It does not guarantee signature
compliance. For e.g., referring back to my example...

class Bar
def method2 param1
end
end

The above implementation of method2 in Bar will cause FooTest to pass
even with responds_like. But the 2 parameter signature is clearly not
supported.

I agree, an integration test should and will catch this scenario and
will fail. But its not possible to write integration tests for every
scenario. After all, the expected method call could happen under a
convoluted condition. A classic case where, I believe, the unit tests
should be able to give me complete guarantee of the state of code.

So there does appear to be a need for stricter checking. Leaving the
default behaviour as is, maybe introduce a new method like
mocked_object.strictly_expects:)blah)...

That leads me to another aspect? How about validating the return types
:D. Java/C# again gave me that additional safety net of knowing that the
return types were correct while I was mocking (at least thats what I
remember).

Pradeep
 
J

James Mead

Sorry for vanishing for a couple of weeks. I have been using ur
suggested approach of 'responds_like'. Although its a good start, it
still does not give me complete confidence. All it tells me is whether
the method is present or not. It does not guarantee signature
compliance. For e.g., referring back to my example...

class Bar
def method2 param1
end
end

The above implementation of method2 in Bar will cause FooTest to pass
even with responds_like. But the 2 parameter signature is clearly not
supported.

I agree, an integration test should and will catch this scenario and
will fail. But its not possible to write integration tests for every
scenario. After all, the expected method call could happen under a
convoluted condition. A classic case where, I believe, the unit tests
should be able to give me complete guarantee of the state of code.

I don't think you have to write an integration test for *every*
scenario. I would aim to write just enough to give decent coverage of
the integration points between classes, probably focussing on common
or important business scenarios.
So there does appear to be a need for stricter checking. Leaving the
default behaviour as is, maybe introduce a new method like
mocked_object.strictly_expects:)blah)...

I have considered something along these lines. I'll give it some more
thought. I've added a feature request on rubyforge [1].
That leads me to another aspect? How about validating the return types
:D. Java/C# again gave me that additional safety net of knowing that the
return types were correct while I was mocking (at least thats what I
remember).

Hmm. I'm not sure how you are suggesting this could be achieved. Ruby
methods do not declare their return type in the method declaration. I
think the best way of obtaining the safety net you describe is to use
integration tests. Alternatively you could go back to a statically
typed language ;-)

--
James.
http://blog.floehopper.org
http://tumble.floehopper.org

[1] http://rubyforge.org/tracker/index.php?func=detail&aid=16769&group_id=1917&atid=7480
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,122
Latest member
VinayKumarNevatia_
Top