With closures it comes natural to make use of the context that is being enclosed. For example, consider the following basic code:
public class Service
{
public void serve(final Consumer<SomeType> callback)
{
final SomeType obj = obtainASomeTypeSomehow();
callback.accept(obj);
}
}
public class MyClass
{
public void myMethod()
{
final Context context = obtainContextSomehow();
service.call(o -> doSomethingWithContext(context));
}
}
Now, to unit test code like this, I would start extracting the anonymous function into its own class, implementing Consumer
, and inject it into MyClass
, so that it can be mocked.
final Service service = mock(Service.class);
final Consumer<SomeType> callback = mock(Consumer.class);
final MyClass sut = new MyClass(service, callback);
sut.myMethod();
// verify something on the mocked callback
The problem, now, comes with the actual implementation of the Consumer
: in the original implementation with the closure, it was directly using the context of the enclosing method, but this is of course not available anymore after I extract it to its own class.
One solution could be to inject that context into the constructor of the callback class, and inject into the main class a factory that creates the callback on the fly with the given context:
final Service service = mock(Service.class);
final Consumer<SomeType> callback = mock(Consumer.class);
final CallbackFactory factory = mock(CallbackFactory.class);
when(factory.create(any()).thenReturn(callback);
final MyClass sut = new MyClass(service, factory);
sut.myMethod();
// verify something on the mocked callback
Then of course I can also write a unit test for the Consumer
implementation, taking the context as a constructor argument.
This could work, but wouldn't all this extracting and injecting go against the whole purpose of using closures? I mean, closures are handy because you can drop a few lines of code in, directly referring to the surrounding context. Is it even possible to unit test code with closures and enclosed contexts, or do I need to always fall back to the more traditional style?
Looking around, this was basically the general suggestion that I saw being given, in addition to moving to a higher (less "unit") level of testing where you avoid mocking too much stuff.