I’ve been using Mockito for writing tests for jSuneido and I really like it.
There is a MockObject in stdlib but it uses the approach of specifying the expected calls beforehand, rather than assertions afterwards. With Mockito, you verify the calls afterwards, which seems more natural.
stdlib also has a separate FakeObject for stubbing. Mockito combines mocking and stubbing.
Mockito also has more powerful capabilities than MockObject and FakeObject and I like their fluent interface, especially since it’s not as easy to make fluent interfaces in statically typed languages like Java.
Here’s what I came up:
Verifying Behaviour
mock = Mock() mock.Func(123, "xyz") mock.Verify.Func(123, "xyz")
The first difference from Mockito is that we don’t have to specify a type when creating a Mock, mocks are “universal”. This is because Suneido is a dynamic language.
The second difference is using mock.Verify.Func rather than Verify(mock).Func. This is because Verify would have to be a global function since Suneido doesn’t have packages or static imports like Java. So it’s nicer to use a method rather than pollute the global namespace.
As with Mockito, if you Verify a call and it was not invoked you get an exception and the test fails.
Note: Normally Func would be called indirectly by the code under test, not directly by the test. So you wouldn’t have the duplication you see in the example above.
Stubbing
mock = Mock() mock.When.Func(123).Return("xyz") Assert(mock.Func(123) is: "xyz") mock.When.Func(-123).Throw("must be positive")
I used mock.When rather than When(mock) for the same reasons as Verify.
Because they are capitalized, and therefore not keywords, I can use Return and Throw rather than Mockito’s thenReturn and thenThrow.
Because Java is statically typed, Mockito can return a suitable default value for a method’s return type. For example, 0 if the method returns int, or false for boolean. In Suneido we don’t know a method’s return type so if a method is not stubbed, it has no return value.
Mockito has to use a different syntax for stubbing void methods with exceptions, but because it’s a dynamic language Suneido doesn’t need that.
Implementation
The code for Mock is actually quite small, but it’s a little tricky. It makes heavy use of Default.
The first step is to make a universal mock that will accept any method call:
class { Default(@call) { } }
Next, to be able to verify calls, we need to track them, so we add to a list of calls:
New() { .calls = Object() } Default(@call) { .calls.Add(call) }
The actual verification is a little trickier. We need mock.Verify which, like Mock, has to accept any method call. Since it’s only used by Mock and it’s small I nested it inside the Mock class.
New() { .Verify = new .verifier(this) .calls = Object() } verifier: class { New(mock) { .mock = mock } Default(@call) { } }
Then we need to actually verify that the call was made on the mock:
Default(@call) { if not .mock.Had?(call) throw "wanted but not invoked" $ .display(call) }
and then in the main Mock class we need to add the Had? method:
Had?(call) { return .calls.Has?(call) }
stubbing is similar to verifying, but a little more complicated because the Default method has to return another object on which we can call Return or Throw:
New() { .Verify = new .verifier(this) .When = new .stubber(this) .calls = Object() .returns = Object() .throws = Object() } Return(call, result) { .returns[call] = result } Throw(call, exception) { .throws[call] = exception } Default(@call) { .calls.Add(call) if .returns.Member?(call) return .returns[call] if .throws.Member?(call) throw .throws[call] } stubber: class { New(mock) { .mock = mock } Default(@call) { return new .aCall(.mock, call) } aCall: class { New(mock, call) { .mock = mock .call = call } Return(result) { .mock.Return(.call, result) } Throw(exception) { .mock.Throw(.call, exception) } } }
Argument Matching
So far, so good. But what if you want to verify or stub with less specific arguments. For example, you want to verify that a method was called but you don’t care what its arguments were. Or you want a method to return false (or throw an exception) regardless of what arguments it gets.
This complicates the code because we can’t just use Has? or Member?, now we have to do some kind of argument matching. And I want more flexibility than just a specific value or any value. Mockito uses the Hamcrest matchers. I recently added similar matchers to Suneido so I can use those.
Next question, what syntax? Again, since Suneido doesn’t have static imports and only a single global name space, I don’t think we want to use global names for the matchers. The obvious choice is to use something like: [startsWith: ‘x’] For now, I’ll just assume that an object with a single named matcher member is a match not a literal object to be matched exactly.
You also want to be able to match any value [any:] and any argument list [anyArgs:]
Mockito has more features than this, but I think this will be enough to start using it.