Joris Schouteden | 4 May 2006 13:29
Picon

Re: stronger typing

Hi,

> record/playback style API.  I find it error prone and that it makes
> tests hard to understand.

It's maybe a bit trickier since you can forget the play() or
startVerification() call, but your test will immediately fail..

Personally (and a lot of my collegues agree), I find string based
expectations a lot more error prone..  In fact, it is _the_ reason why we
are currently looking at other frameworks.

I agree two states in the mock object is ugly, a solution could be to
expose two proxies in the mock, one to be used in the test as usual, the
other to set expectations on:

    public T proxy();
    public T proxyForExpectations();

>> That sounds less intrigued than Nat's response in the "Typesafe
>> expectations" thread from February/March.  Joris, how would you contrast
>> your approach to the one outlined in that thread?

It doesn't look that much different, in fact I see mainly syntactical
differences between the way of working of this, easymock, rmock, jmock
etc..
Just different ways to overcome the java limitations Nat talked about and
make it as easy/compact as possible for the user.

Anyway, I've attached my jmock extension together with it's test and an
introduction text.  I know it has some rough edges :)

Joris
 Introducation

SemiTypedMock is a small extension to jMock that allows you to set expectation using the actual interface
that is mocked, like EasyMock or RMock.

Note that since SemiTypedMock inherits from Mock you can still use the standard jMock style expectations.

[edit]
Expectations

Imagine you want to mock the following interface:

public interface FunkyManager
{
	public void doSomething(Map argument);
	public String getSomething() throws FunkyException;
}

In your test you can mock this interface similar to regular jMock:

SemiTypedMock funkyManagerMock = new SemiTypedMock(FunkyManager.class);
FunkyManager funkyManager = funkyManagerMock.proxy();

You can add (record) expectations:

funkyManagerMock.record();
funkyManager.doSomething(new HashMap());

This is the same as doing

funkyManagerMock.expects(once()).method("doSomething").with(eq(new HashMap());

Later you can play back these expectations to validate your test:

funkyManagerMock.play();
// do code that calls the expected methods
funkyManagerMock.verify();

You can specify a return a value or throw an exception. The default behaviour is to return null, 0 or false.

funkyManagerMock.next().will(returnValue("lala"));
funkyManager.getSomething();
funkyManagerMock.next().will(throw(new FunkyException()));
funkyManager.getSomething();

This is the same as

funkyManagerMock.expects(once()).method("getSomething").will(returnValue("lala"));
funkyManagerMock.expects(once()).method("getSomething").will(throwException(new FunkyException());

Sometimes you need more control over the arguments that you expect. You can override this with the usual
jMock constraints (eq, isA, any, notNull, etc). The default is eq.

funkyManagerMock.next().with(any());
funkyManager.doSomething(null);

Or if you need more control over how many times something can be called, you can use the standard jMock
invocation matchers. The default is once().

funkyManagerMock.expects(atLeastOnce());
funkyManager.doSomething(null);

You can ofcourse combine all this:

funkyManagerMock.expects(atLeastOnce()).with(isA(String.class)).will(returnValue("ok"));
funkyManager.doSomething(null);

You can also stub methods in the same way as jMock:

funkyManagerMock.stubs();
funkyManager.doSomething();

[edit]
Ordering

You can specify a specific call order. The default is no ordering.

funkyManagerMock.ordered(true);
funkyManager.doSomething(map1);
funkyManager.doSomething(map2);
funkyManager.doSomething(map3);

[edit]
Extra's

You can stub out (ignore) all methods in one go

funkyManagerMock.stubAll();

Gmane