Skip to content

Instantly share code, notes, and snippets.

@bcoughlan
Created September 3, 2023 22:45
Show Gist options
  • Save bcoughlan/5dfdb37cabd3939685667ca9cc914be1 to your computer and use it in GitHub Desktop.
Save bcoughlan/5dfdb37cabd3939685667ca9cc914be1 to your computer and use it in GitHub Desktop.
Mock an interface by proxying to a a real class

Mocking complex behavior with Mockito's doAnswer() and ArgumentCaptor can quickly get messy. But creating test doubles for interfaces that have many methods requires a lot of boilerplate and increases maintenance overhead.

This code allows you to create a test double without implementing a common interface with the class to be mocked.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
public class MockUtils {
/**
* Proxy an interface to a real implementation. The implementation doesn't have to implement the interface,
* which can save on a lot of boilerplate for test doubles.
*/
public static <T> T proxyToFake(Class<T> iface, Object forwardTo) {
var forwarder = Proxy.newProxyInstance(
iface.getClassLoader(),
new Class[]{iface},
(proxy, method, args) -> {
try {
var delegateMethod = forwardTo.getClass().getMethod(method.getName(), method.getParameterTypes());
return delegateMethod.invoke(forwardTo, args);
} catch (NoSuchMethodException e) {
throw new UnsupportedOperationException("Method not implemented in forwarding class: " + method);
} catch (InvocationTargetException e) {
throw e.getCause();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
);
return (T) forwarder;
}
}
class FakeSpringRepositoryDelete {
private final List<Long> deleted = new ArrayList<>();
public void deleteAllById(Iterable<Long> ids) {
deleted.addAll(ids);
}
}
@Test
void myTest() {
var testDouble = new FakeSpringRepositoryDelete();
var testDoubleProxy = MockUtils.proxyToFake(MySpringDataRepository.class, testDouble);
var classUnderTest = new MyService();
classUnderTest.doSomething();
assertThat(testDouble).containsOnly(1, 2, 3)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment