Can one of the popular Java mocking frameworks like EasyMock or Mockito be used to mock Clojure protocols defined with defprotocol
? If so, how?
You should be able to mock protocols using any mock library. Under the covers, every protocol uses a Java interface as an implementation detail, and you could just mock that interface.
That said, don't do this! Mocking in Java is absurdly complex because of reflection, protection levels, final classes, etc. Any time you want a Clojure object that implements a protocol, simply call reify, e.g.
(defprotocol Foo (method-a [_]) (method-b [_]))
-> Foo
(let [stub (reify Foo (method-a [_] :stubbed))]
(method-a stub))
-> :stubbed
Note that you need not stub the methods you don't plan to call.
It looks like the more recent versions of Midje provide this functionality quite nicely.
First, I would like to note that this kind of mocking is very commonly useful when splitting larger programs into components (e.g. that are assembled via dependency injection with libraries like Stuart Sierra's component library). If I've got some component that isolates a set of side-effect functions into a conceptual component I certainly want a testing framework that will let me inject a stand-in component so that I can:
You could use Mockito or some other library, but I agree such a solution is not going to be particularly elegant.
Unfortunately, protocols and records generate classes that Midje cannot hack into as easily as functions...so you do have to modify your code slightly:
(defrecord-openly SideEffectThing [connection]
ISideEffect
(persist [this thing] :unfinished)
(read [this] :unfinished)
)
See Midje's documentation on production mode for details about how to make this modification not affect your production runtime.
By defining your component with defrecord-openly, you gain the ability to specify the behavior of the component's methods using Midje's "provided" mechanism:
(fact "you can test in terms of a record's methods"
(let [obj (->SideEffectThing :fake-connection)]
(user-of-side-effect-thing obj) => 0
(provided
(read obj) => -1)
)
)
You could, of course, avoid relying on a production type here (which I would advocate), and also avoid peppering defrecord-openly throughout your production code. In this case, just move SideEffectThing (as written above) into your test code. Then your component system in the application can plug in the real component, but your tests can be written against this unimplemented test version.
To be complete, I will compare the equivalent Java Mockito code with the above solution. In Java:
interface ISideEffect { int read(); void write(Object something); }
class SideEffectThing implements ISideEffect { ... }
// in test sources:
public class SomeOtherComponentSpec {
private ISideEffect obj;
@Before
public void setup() { obj = mock(ISideEffect.class); }
@Test
public void does_something_useful() {
when(obj.read()).thenReturn(-1);
OtherComponent comp = new OtherComponent(obj);
int actual = comp.doSomethingUseful();
assertEquals(0, actual);
verify(obj).read();
}
this Java solution mocks out the component, specifies required behavior of that component, and then not only checks that the component itself works properly, but also that the component depended on the call to read() in some way. Mockito also supports pattern matching on arguments (and capture) to analyze how the component was used, if needed.
The Midje example above does much of that, and in a much clearer form. If you indicate (in the provided clause) that the function is called with specific arguments, then the test will fail if it isn't. If you specify that the function is called more than once (and it isn't), then that is a failure. For example, to indicate the read will be called 3 times and should return different values:
(fact "you can test in terms of a record's methods"
(let [obj (->SideEffectThing :fake-connection)]
(user-of-side-effect-thing obj) => 0
(provided
(read obj) => -1
(read obj) => 6
(read obj) => 99
)
)
)
indicates that you expect read to be called three times, and that it should return the specified sequence of values. See the docs on prerequisites for more details, including how to specify exact number of times on a single function spec in provided, and how to indicate the function is never called.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With