I have the following Java static instance, MyRegistry, which is used to invoke service layer methods.
Up until now it operated in a synchronous fashion, providing 2 methods.
public final class MyRegistry {
private static MyRegistry instance;
public static MyRegistry getInstance() {
if (instance == null) {
instance = new MyRegistry();
}
return instance;
}
public <T> T invoke(MyService myService, String methodName) {
return myService.invoke(methodName);
}
public <T> T invoke(MyService myService, String methodName, Object[] params) {
return myService.invoke(methodName, params);
}
}
There is now a requirement to provide 2 new asynchronous methods, using CompletableFuture instances as the return objects.
I'm confused about how these 4 method signatures should be written.
The 4 signatures below were suggested, the idea is that all methods regardless of sync/async would return a CompletableFuture.
However wouldn't this mean it would be up to the consumer of the API, whether the operation is done synchronously or asynchronously based on the method they invoke on the returned object?
i.e by using either .get() to block or .thenAcceptAsync to run async, on the returned CompletableFuture?
Meaning, what would be the point of providing invoke vs invokeAsync alternatives?
public CompletableFuture invoke(MyService myService, String methodName, Object[] params) {
}
public CompletableFuture invoke(MyService myService, String methodName) {
}
public CompletableFuture invokeAsync(MyService myService, String methodName, Object[] params) {
}
public CompletableFuture invokeAsync(MyService myService, String methodName) {
}
Related
When using Spring Cglib proxy, we need to implement a MethodInterceptor callback, I have some problems about this callback. To make it clearer, let's use a simple example.
Here is my target class MyPlay.java
public class MyPlay {
public void play() {
System.out.println("MyPlay test...");
}
}
And I created a callback:
public class CglibMethodInterceptor implements MethodInterceptor {
private Object target;
public CglibMethodInterceptor(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
#Override
public Object intercept(
Object o,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB prep work...");
Object obj = method.invoke(target, objects);
System.out.println("CGLIB post work...");
return obj;
}
}
In my Main class:
MyPlay myPlay = new MyPlay();
cglibMethodInterceptor = new CglibMethodInterceptor(myPlay);
Play myPlayProxy = (Play) cglibMethodInterceptor.getProxy();
myPlay.play();
myPlayProxy.play();
I'm confused about the meaning of the parameters of the intercept method:
#Override
public Object intercept(
Object o,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
}
So, I set up a breakpoint to at the myPlayProxy.play() and step into it. I took a screenshot:
Problem: What are the method and methodProxy parameters? What is the difference between them? When I use the methodProxy to invoke, it also works, which confuses me.
Object obj = method.invoke(target, objects);
// This also works, why?
// Object obj = methodProxy.invoke(target, objects);
The Javadoc says:
The original method may either be invoked by normal reflection using the Method object, or by using the MethodProxy (faster).
I don't know what makes it faster.
Is there any way, using Mockito or PowerMockito, to intercept calls to non-static methods of an object, or at least of a singleton object?
An example is provided by the following classes:
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton(Object parameter) {}
public static Singleton getInstance(Object parameter) {
if (INSTANCE == null) {
INSTANCE = new Singleton(parameter);
}
return INSTANCE;
}
public String process(String a, String b) {
return (a + b);
}
// Other methods
}
public class Foreign {
private Foreign() {}
public static void main(String[] args) {
System.out.println(Singleton.getInstance(new Object()).process("alpha", "beta"));
}
}
The Singleton object is created in a Foreign class, outside the control of some test code (not shown above). Neither of these two classes can be modified. The objective is to intercept calls to the non-static process() method in the test code so that, for certain values, a different result is returned, e.g. the call
Singleton.getInstance(new Object()).process("alpha", "beta");
mocked to return "alpha-beta" instead of the expected "alphabeta".
One solution could be intercepting the Singleton.getInstance() method to instantiate a custom subclass of the Singleton, e.g. using
public class SubSingleton extends Singleton {
public SubSingleton(Object parameter) {
super(parameter);
}
public String process(String a, String b) {
if ("alpha".equals(a) && "beta".equals(b)) {
return a + "-" + b;
}
return super.process(a + b);
}
}
Then, calls to the Singleton.process() method would be intercepted as in:
Object parameter = new Object();
PowerMockito.doReturn(new SubSingleton(parameter)).when(Singleton.class, "getInstance", parameter);
However, the Singleton class above only provides a private constructor, so it cannot be extended. Using PowerMockito.whenNew() to return a partial mock (spy) will also not work, since the Singleton class does not provide a no-args constructor.
Can the desired mocking be implemented in any other way? Can it be done for non-singleton classes?
Firstly, you can use whenNew for objects with constructor with some params:
#RunWith(PowerMockRunner.class)
#PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
#Mock
Singleton singletonMock;
#Before
public void setUp() throws Exception {
PowerMockito.whenNew(Singleton.class)
.withAnyArguments()
.thenReturn(singletonMock);
}
#Test
public void testMockNew() throws Exception {
Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
Foreign.main(new String[0]);
}
}
Secondly, why not stub getInstance instead of new:
#RunWith(PowerMockRunner.class)
#PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
#Test
public void testMockNew() {
PowerMockito.mockStatic(Singleton.class);
Singleton singletonMock = Mockito.mock(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
Foreign.main(new String[0]);
}
}
Thirdly, to intercept the process method:
create real singleton
create a mock singleton
mock static getInstance to return the mock. NOTE: you must call mockStatic after getting real instance.
use thenAnswer to check the arguments on process call
return desired answer if they match desired pattern
else call real method on real singleton
#RunWith(PowerMockRunner.class)
#PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
#Test
public void testMockNew() {
var singletonReal = Singleton.getInstance(new Object());
var singletonMock = Mockito.mock(Singleton.class);
PowerMockito.mockStatic(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process(anyString(), anyString())).thenAnswer((args) -> {
String a = args.getArgument(0);
String b = args.getArgument(1);
if ("alpha".equals(a) && "beta".equals(b)) {
return "sasa";
} else {
return singletonReal.process(a, b);
}
});
Foreign.main(new String[0]);
}
}
And finally, use a spy instead of a mock
#RunWith(PowerMockRunner.class)
#PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
#Test
public void testMockNew() {
var singletonReal = Singleton.getInstance(new Object());
var singletonMock = Mockito.spy(singletonReal);
PowerMockito.mockStatic(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process("alpha", "beta")).thenReturn("sasa");
// NOTE: real method is called for other args
Foreign.main(new String[0]);
}
}
I'm using Mockito to test a method that internally makes a networking call and returns a value based on the result of the networking call. This method uses a SynchronousQueue to wait for the result, and the result is set by the callback for the networking call:
HelperClass helperClassObject = new HelperClassObject();
...
public SomeResultCode methodWithNetworkCall() {
SynchronousQueue<SomeResultCode> resultQueue = new SynchronousQueue<>();
// some condition checking code
helperClassObject.makeNetworkCall(new GenericCallback() {
#Override
public void onSuccess(JSONObject response) {
resultQueue.offer(SomeResultCode.SUCCESS);
}
#Override
public void onFailure(VolleyError error) {
resultQueue.offer(SomeResultCode.FAILURE);
}
});
SomeResultCode resultCode = null;
try {
resultCode = resultQueue.poll(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
}
return resultCode == null ? SomeResultCode.FAILURE : resultCode;
}
In one of my unit test methods I'm trying to verify that SUCCESS is returned upon successful network call. I've tried using ArgumentCaptor and doAnswer to trigger the callback's onSuccess. However, the method is returning FAILURE. I put a breakpoint in the onSuccess, and it looks like when I use the ArgumentCaptor way the onSuccess is triggered AFTER the poll has timed out. When I use the doAnswer way, I see onSuccess called during the setup (doAnswer.when) but not after I actually call the method. What am I doing wrong?
EDIT
Stepping through the code again, it looks like answer is called from within the method I'm testing (i.e. when I call testObject.methodWithNetworkCall during my test), NOT during setup. So it is doing exactly what it is supposed to do: responding with onSuccess. But it is responding with onSuccess BEFORE poll is called. So it seems the problem is not that answer and mocking in general is not working/set up wrong, it is an issue with testing with SynchronousQueue.
Here is my test code:
public class TestClassUnitTest {
TestClass sut;
HelperClass helperClassObject = mock(HelperClass.class);
#Before
public void setup() {
sut = new TestClass();
injectField(sut, "helperClassFieldName", helperClassObject);
}
public void injectField(Object testObject, String fieldName, T mockToInject) {
// some code using reflection to inject the mock object into the test object
}
#Test
public void testMethodWithNetworkCallWithCaptor() {
ArgumentCaptor<GenericCallback> captor = ArgumentCaptor.forClass(GenericCallback.class);
SomeResultCode result = sut.methodWithNetworkcall();
verify(helperClassObject, times(1)).makeNetworkCall(captor.capture());
captor.getValue().onSuccess(new JSONObject());
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
#Test
public void testMethodWithNetworkCallWithDoAnswer() {
doAnswer(new Answer(){
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((GenericCallback)invocation.getArguments()[0]).onSuccess(new JSONObject());
return null;
}
}).when(helperClassObject).makeNetworkCall(any(GenericCallback.class));
SomeResultCode result = sut.methodWithNetworkcall();
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
}
It looks like you're not replacing your HelperClassObject in your system-under-test, or at least you haven't shown us where you have. The mock returned by Mockito.mock (or #Mock or spy or #Spy) doesn't apply to every instance of the class you pass in; it just creates a single instance. You have to make sure to set the instance (HelperClassObject here) in your system-under-test, possibly by passing it in as a constructor parameter, setting the instance as a field, or setting it using a setter method. If you leave it as new HelperClassObject() as you've shown us, there's no way Mockito will be able to help you.
Your reference to "onSuccess called during the setup (doAnswer.when)" worries me a little bit, because if you've created a mock using Mockito.mock, there should be no reason Mockito would actually call your Answer during setup. This leads me to believe that your HelperClassObject or makeNetworkcall method can't be mocked, possibly from having limited visibility, or because they're marked static or final. Mockito effectively works by writing a custom subclass of the class you're mocking, so make sure the classes and methods you're mocking are public and non-final to ensure they're overridable. (It is possible to mock protected or package-private methods, but certain versions of Mockito have complications with certain code structures. Let's rule that out first.)
After you make sure that the class is mockable and that it's using the mocked HelperClassObject instance you pass in, you'll be able to move forward. You'll want to pursue the doAnswer structure: The ArgumentCaptor version won't work, because if your methodWithNetworkcall blocks and waits for a result, then you'll get a FAILURE return value before you ever get a chance to verify and call your callback. (That explains the timeout.) In other cases where your method-under-test can return first, the ArgumentCaptor solution will be more practical for you.
In this case using doAnswer IS the correct approach. The issue is with the way SynchronousQueue worked: it expects multi-threaded usage of this queue:
A blocking queue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.
But in this testing case the test runs on a single thread.
Solution: mock the SynchronousQueue, and use doAnswer to get offer() and poll() to push/pop result onto a LinkedList. In the process, I also moved the SynchrnousQueue local variable resultQueue out of methodWithNetworkCall() and made it an instance member. Updated test code below:
public class TestClassUnitTest {
TestClass sut;
private LinkedList testQueue = new LinkedList();
private SynchronousQueue<SomeResultCode> resultQueueMock = mock(SynchronousQueue.class);
private HelperClass helperClassMock = mock(HelperClass.class);
#Before
public void setup() {
sut = new TestClass();
injectField(sut, "resultQueue", resultQueueMock);
injectField(sut, "helperClassFieldName", helperClassMock);
}
public void injectField(Object testObject, String fieldName, T mockToInject) {
// some code using reflection to inject the mock object into the test object
}
#Test
public void testMethodWithNetworkCallWithDoAnswer() {
doAnswer(new Answer(){
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((GenericCallback)invocation.getArguments()[0]).onSuccess(new JSONObject());
return null;
}
}).when(helperClassMock).makeNetworkCall(any(GenericCallback.class));
mockQueue();
SomeResultCode result = sut.methodWithNetworkCall();
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
private void mockQueue() {
doAnswer(new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
testQueue.push(((SchedulableJob.Result)invocation.getArguments()[0]));
return true;
}
}).when(resultQueueMock).offer(any());
try {
doAnswer(new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (testQueue.size() > 0) {
return testQueue.pop();
} else {
return null;
}
}
}).when(resultQueueMock).poll(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
}
}
}
Please have a look at the following code:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
printMethodName(MyClass::myMethod);
Full example:
#FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
In other words I would like to have a code which is the equivalent of the following C# code:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
In my case I was looking for a way to get rid of this in unit tests:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPoint and checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
From the ideas by #ddan I built a proxy solution using Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
final String methodName = getMethodName(object.getClass(), getter);
assertEquals(getter.apply(object), expected, methodName);
}
#SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
final Method[] method = new Method[1];
getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
})));
return method[0].getName();
}
No I can simply use
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
Downside:
Will be slightly slower than above
Needs Mockito to work
Hardly useful to anything but the usecase above.
However it does show a way how it could be done.
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
You can add safety-mirror to your classpath and do like this:
Method m1 = Types.createMethod(Thread::isAlive) // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
The library also offers a getName(...) method:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of.
See fx Holger's comment here: How to get the name of the method resulting from a lambda
There may not be a reliable way, but under some circumstances:
your MyClass is not final, and has an accessible constructor (limitation of cglib)
your myMethod is not overloaded, and not static
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.
Example code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
public class MethodReferenceUtils {
#FunctionalInterface
public static interface MethodRefWith1Arg<T, A1> {
void call(T t, A1 a1);
}
public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
return findReferencedMethod(clazz, t -> methodRef.call(t, null));
}
#SuppressWarnings("unchecked")
private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
AtomicReference<Method> ref = new AtomicReference<>();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
#Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ref.set(method);
return null;
}
});
try {
invoker.accept((T) enhancer.create());
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
Method method = ref.get();
if (method == null) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
return method;
}
}
In the above code, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.
If you can make the interface Action extend Serializable, then this answer from another question seems to provide a solution (at least on some compilers and runtimes).
We have published the small library reflection-util that can be used to capture a method name.
Example:
class MyClass {
private int value;
public void myMethod() {
}
public int getValue() {
return value;
}
}
String methodName = ClassUtils.getMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"
Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name.
ClassUtils caches the information such that we do not need to create a new proxy on every invocation.
Please note that this approach is no silver bullet and there are some known cases that don’t work:
It doesn’t work for static methods.
It doesn’t work if the class is final.
We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
You can use my library Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Another solution using Mockito:
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>compile</scope>
</dependency>
Test code:
public class MethodUtilTest {
#Test
void testMethodNameGetter() {
final Method method = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
Assertions.assertEquals("getTestString", method.getName());
}
#Test
void testMethodNameSetter() {
final Method method = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
Assertions.assertEquals("setTestString", method.getName());
}
}
java code:
public class MethodUtil {
public static <T> Method getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
return captureMethodOnInvocation(clazz, getter::apply);
}
public static <T, V> Method getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
return captureMethodOnInvocation(clazz, (T mock) -> setter.accept(mock, ArgumentMatchers.any()));
}
private static <T> Method captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
try {
final AtomicReference<Method> methodReference = new AtomicReference<>();
final InvocationListener invocationListener = new InvocationListener() {
#Override
public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
final Method method = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
methodReference.set(method);
}
};
final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
final T mock = Mockito.mock(clazz, mockSettings);
invokeMock.accept(mock);
return methodReference.get();
} catch (final Exception e) {
throw new RuntimeException("Method could not be captured at runtime.", e);
}
}
}
So, I play with this code
import sun.reflect.ConstantPool;
import java.lang.reflect.Method;
import java.util.function.Consumer;
public class Main {
private Consumer<String> consumer;
Main() {
consumer = this::test;
}
public void test(String val) {
System.out.println("val = " + val);
}
public void run() throws Exception {
ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
for (int i = 0; i < oa.getSize(); i++) {
try {
Object v = oa.getMethodAt(i);
if (v instanceof Method) {
System.out.println("index = " + i + ", method = " + v);
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
output of this code is:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30.
Final code may look like
public Method unreference(Object methodRef) {
ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
try {
Object method = constantPool.getMethodAt(30);
if (method instanceof Method) {
return (Method) method;
}
}catch (Exception ignored) {
}
throw new IllegalArgumentException("Not a method reference.");
}
Be careful with this code in production!
Try this
Thread.currentThread().getStackTrace()[2].getMethodName();
I need to test handleIn() method using Mockito.
However the code need to call this legacy code Util.getContextPDO which is a static method.
Note that in testing environment this Util.getContextPDO is always returns Exception, and I intend to bypass this Util.getContextPDO() by always return a dummy IPDO.
public class MyClass {
public IPDO getIPDO()
{
return Util.getContextPDO(); // note that Util.getContextPDO() is a static, not mockable.
}
public String handleIn(Object input) throws Throwable
{
String result = "";
IPDO pdo = getIPDO();
// some important business logic.
return result;
}
}
Initially I thought this achieveable by using spy() of the class "MyClass", so I can mock the return value of getIPDO(). Below is my initial effort using spy ()
#Test
public void testHandleIn() throws Exception
{
IPDO pdo = new PDODummy();
MyClass handler = new MyClass ();
MyClass handler2 = spy(handler);
when(handler2.getIPDO()).thenReturn(pdo);
PDOUtil.setPDO(pdo, LogicalFieldEnum.P_TX_CTGY, "test123");
IPDO pdoNew = handler2.getIPDO();
Assert.assertEquals("test123,(PDOUtil.getValueAsString(pdoNew, LogicalFieldEnum.P_TX_CTGY)));
}
However the when(handler2.getIPDO()).thenReturn(pdo); is throwing the Exception that I want to avoid ( because handler2.getIPDO() ) seems to call the real method.
Any idea on how to test this part of code?
A good technique for getting rid of static calls on 3rd party API is hiding the static call behind an interface.
Let's say you make this interface :
interface IPDOFacade {
IPDO getContextPDO();
}
and have a default implementation that simply calls the static method on the 3rd party API :
class IPDOFacadeImpl implements IPDOFacade {
#Override
public IPDO getContextPDO() {
return Util.getContextPDO();
}
}
Then it is simply a matter of injecting a dependency on the interface into MyClass and using the interface, rather than the 3rd party API directly :
public class MyClass {
private final IPDOFacade ipdoFacade;
public MyClass(IPDOFacade ipdoFacade) {
this.ipdoFacade = ipdoFacade;
}
private IPDO getIPDO() {
return ipdoFacade.getContextPDO();
}
public String handleIn(Object input) throws Throwable
{
String result = "";
IPDO pdo = getIPDO();
someImportantBusinessLogic(pdo);
return result;
}
...
}
In your unit test, you can then easily mock your own interface, stub it any way you like and inject it into the unit under test.
This
avoids the need to make private methods package private.
makes your tests more readable by avoiding partial mocking.
applies inversion of control.
decouples your application from a specific 3rd party library.
Changed my testing to :
#Test
public void testHandleIn() throws Exception
{
IPDO pdo = new PDODummy();
MyClass handler = new MyClass ();
MyClass handler2 = spy(handler);
doReturn(pdo ).when( handler2 ).getIPDO();
PDOUtil.setPDO(pdo, LogicalFieldEnum.P_TX_CTGY, "test123");
IPDO pdoNew = handler2.getIPDO();
Assert.assertEquals("test123,(PDOUtil.getValueAsString(pdoNew, LogicalFieldEnum.P_TX_CTGY)));
}
Solved after reading Effective Mockito.
when(handler2.getIPDO()).thenReturn(pdo);
Will actually call the method and then return pdo regardless.
Whereas:
doReturn(pdo).when(handler2).getIPDO();
Will return pdo without calling the getIPDO() method.