I'm using EasyMock to create mock objects for JUnit testing in Java. I create a mock object and pass it to another thread where it expects methods to be called. In the other thread, the calls are enclosed in a try/catch(Throwable) block, so when an unexpected call occurs on the mock and it thus throws AssertionError, that error is caught by the catch block and treated. So, even though an unexpected call occurred, the test passes.
In order to have the test fail as expected, I would like to delay all verification of calls to the EasyMock.verify(mock) call made in the test-runner thread at the end. Is this possible and how?
I'm not sure about how to do this with EasyMock, but this behavior is possible with Mockito because verification assertions can be specified at the end of the test.
The correct solution I'd guess is to stop catching Throwable. Doing so catches all Errors as you're finding, which can be quite dangerous... are you absolutely positively 100% sure you need to catch Throwable? Why?
(If it turns out you do, you could catch AssertionError specifically and rethrow it. But that's ugly!)
Try using nice mocks:
http://easymock.org/EasyMock2_5_2_Documentation.html
"Nice Mocks
On a Mock Object returned by createMock() the default behavior for all methods is to throw an AssertionError for all unexpected method calls. If you would like a "nice" Mock Object that by default allows all method calls and returns appropriate empty values (0, null or false), use createNiceMock() instead. "
Default values will be returned for unexpected calls instead of throwing AssertionError, but you can still verify them with the verify() method (in which case the AssertionErrors will be thrown)
As #deterb suggested, it's possible with Mockito but you have to know the method name or you have to set expectations for every method. Here is an example:
The mocked interface:
public interface MyInterface {
void allowedMethod();
void disallowedMethod();
}
The user class which catches AssertionError:
public class UserClass {
public UserClass() {
}
public static void throwableCatcher(final MyInterface myInterface) {
try {
myInterface.allowedMethod();
myInterface.disallowedMethod();
} catch (final Throwable t) {
System.out.println("Catched throwable: " + t.getMessage());
}
}
}
And the Mockito test:
#Test
public void testMockito() throws Exception {
final MyInterface myInterface = mock(MyInterface.class);
UserClass.throwableCatcher(myInterface);
verify(myInterface, never()).disallowedMethod(); // fails here
}
The same is possible with EasyMock but it needs some work:
#Test
public void testEasyMock() throws Exception {
final AtomicBoolean called = new AtomicBoolean();
final MyInterface myInterface = createMock(MyInterface.class);
myInterface.allowedMethod();
myInterface.disallowedMethod();
final IAnswer<? extends Object> answer = new IAnswer<Object>() {
#Override
public Object answer() throws Throwable {
System.out.println("answer");
called.set(true);
throw new AssertionError("should not call");
}
};
expectLastCall().andAnswer(answer).anyTimes();
replay(myInterface);
UserClass.throwableCatcher(myInterface);
verify(myInterface);
assertFalse("called", called.get()); // fails here
}
Unfortunately you also have to know the method names here and you have to define expectations like myInterface.disallowedMethod() and expectLastCall().andAnswer(answer).anyTimes().
Another possibility is creating a proxy with the Proxy class (with a custom InvocationHandler) and using it as a mock object. It definitely needs more work but it could be the most customizable solution.
Finally don't forget that it's also possible to create a custom implementation with or without delegation to the EasyMock mock object. Here is one with delegation:
public class MockedMyInterface implements MyInterface {
private final MyInterface delegate;
private final AtomicBoolean called = new AtomicBoolean();
public MockedMyInterface(final MyInterface delegate) {
this.delegate = delegate;
}
#Override
public void allowedMethod() {
delegate.allowedMethod();
}
#Override
public void disallowedMethod() {
called.set(true);
throw new AssertionError("should not call");
}
public boolean isCalled() {
return called.get();
}
}
And the test for it:
#Test
public void testEasyMockWithCustomClass() throws Exception {
final MyInterface myInterface = createMock(MyInterface.class);
myInterface.allowedMethod();
final MockedMyInterface mockedMyInterface =
new MockedMyInterface(myInterface);
replay(myInterface);
UserClass.throwableCatcher(mockedMyInterface);
verify(myInterface);
assertFalse("called", mockedMyInterface.isCalled()); // fails here
}
Related
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) {
}
}
}
I am using Mockito services to test the Exception that could be thrown in MyFinalClass2 and be caught in MyAbstractClass as the concrete method makes a call to the getValue method in MyFinalClass2. This method returns an interface (MyInterfaceClass) object.
I asked a question earlier in Mocking Chained calls in Concrete Class Mockito and thankfully, the solution provided works when calling the getObject method found in MyFinalClass . Therefore, test1 works!
However, this time I have another final class (MyFinalClass2) which is not #Autowired, and is called after a call to a method in MyFinalClass. An Object of type MyFinalClass2 is return from the first call to method in MyFinalClass. When manually throwing a MyException2 in test2 , it does not get recognized and therefore causes an AssertionFailure in Test2 .
Also please note, the below line of code returns NULL as it has not yet been implemented. Which is the reason why I am returning in Test2 a new instance of MyFinalClass2 when this method is called.
MyFinalClass2 myFinalClass2 = getObject(strName);
Please see below code.
public abstract class MyAbstractClass{
#Autowired
private MyFinalClass myFinalClass;
//concrete method
protected MyInterfaceClass myConcreteMethod(String strName, String str)
{
try{
//returns null as it has not yet been implemented
MyFinalClass2 myFinalClass2 = getObject(strName);
MyInterfaceClass b = getValue(myFinalClass2,str);
return b;
} catch(MyException e){
LOGGER.log("ERROR THROWN" + e);
} catch(MyException2 e){
LOGGER.log("ERROR THROWN" + e);
}
}
public MyFinalClass2 getObject(String strName){
return myFinalClass.getObject(strName);
}
public MyInterfaceClass getValue(MyFinalClass2 myFinalClass2, String
str){
return myFinalClass2.getValue(str);
}
}
public final class MyFinalClass {
public MyFinalClass2 getObject(String strName) throws MyException{
**** HAS NOT YET BEEN IMPLEMENTED ****
return null;
}
}
public final class MyFinalClass2 {
public MyInterfaceClass getValue(String str) throws MyException2{
**** HAS NOT YET BEEN IMPLEMENTED ****
return null;
}
}
public interface MyInterfaceClass {
**** HAS NOT YET BEEN IMPLEMENTED BY ANY CLASS ****
void getStuff();
}
#ContextConfiguration(locations = "classpath:applicationContext-test.xml")
#RunWith(PowerMockRunner.class)
public class MyAbstractClassTest {
public static class ExampleConcreteClass extends MyAbstractClass{
}
#InjectMocks
#Spy
ExampleConcreteClass exampleConcreteClass;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
//THIS WORKS --- TEST 1
#Test
public void testIfExceptionOneIsThrown(){
try{
Mockito.when(exampleConcreteClass).getObject(name).
thenThrow(new MyException());
exampleConcreteClass.myConcreteMethod();
Assert.fail();
}catch(MyException e){
Assert.assertTrue(Boolean.TRUE);
}
}
//This test DOES NOT work --- TEST 2
#Test
public void testIfExceptionTwoIsThrown(){
try{
MyFinalClass2 myFinClass2 = new MyFinalClass2();
String strName = "name";
String str = "str";
Mockito.when(exampleConcreteClass).getValue(myFinClass2,str).
thenThrow(new MyException2());
Mockito.when(exampleConcreteClass).getObject(strName).
thenReturn(myFinClass2);
exampleConcreteClass.myConcreteMethod();
Assert.fail();
}catch(MyException e){
Assert.fail();
}catch(MyException2 e){
Assert.assertTrue(Boolean.TRUE);
}
}
}
Please help. Greatly Appreciated!
First of all, I am sorry to say so, but if you need to think that hard about testing, it's normally a sure sign that the code is bad. Anway, you don't need PowerMock there, since Mockito 2 is able to mock final classes already (you have to opt-in for that, though).
Also your test doesn't make any sense at all, since your exampleConcreteClass.myConcreteMethod() cannot throw a MyException2, because...
} catch(MyException2 e){
LOGGER.log("ERROR THROWN" + e);
}
...thus the MyException2 will never leave the scope of the method, but be transformed into a log message there and then be discarded. It will not leave the method, thus trying to assert it will not work. You would have something like throw e; there, too:
} catch(MyException2 e){
LOGGER.log("ERROR THROWN" + e);
throw e; // re-throw e, otherwise it ends here
}
Also, just as a hint, what you are trying to do there can be more easily done by simple writing...
#Test(expected=MyException2.class)
public void testIfExceptionTwoIsThrown() throws MyException, MyException2 {
...this way, you can remove all the try-catch stuff from your test method, since JUnit will automatically fail the test if no MyException2 is thrown. Alternatively, if you want to have more control, you can even have a look at JUnit's ExpectedException rule, which is a little bit more powerful and versatile than just the expected parameter (but that one should be enough for this case).
But of course, nothing changes the fact that, as long as your method catches your MyException2, it will not leave said method, so testing for it will not help.
How do I use Hamcrest to test for an exception? According to a comment in https://code.google.com/p/hamcrest/wiki/Tutorial, "Exception handling is provided by Junit 4 using the expected attribute."
So I tried this and found that it worked:
public class MyObjectifyUtilTest {
#Test
public void shouldFindFieldByName() throws MyObjectifyNoSuchFieldException {
String fieldName = "status";
String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName);
assertThat(field, equalTo(fieldName));
}
#Test(expected=MyObjectifyNoSuchFieldException.class)
public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException {
String fieldName = "someMissingField";
String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName);
assertThat(field, equalTo(fieldName));
}
}
Does Hamcrest provide any additional functionality above and beyond the #Test(expected=...) annotation from JUnit?
While someone asked about this in Groovy (How to use Hamcrest to test for exception?), my question is for unit tests written in Java.
Do you really need to use the Hamcrest library?
If not, here's how you do it with Junit's support for exception testing. The ExpectedException class has a lot of methods that you can use to do what you want beyond checking the type of the thrown Exception.
You can use the Hamcrest matchers in combination with this to assert something specific, but it's better to let Junit expect the thrown exceptions.
public class MyObjectifyUtilTest {
// create a rule for an exception grabber that you can use across
// the methods in this test class
#Rule
public ExpectedException exceptionGrabber = ExpectedException.none();
#Test
public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException {
String fieldName = "someMissingField";
// a method capable of throwing MyObjectifyNoSuchFieldException too
doSomething();
// assuming the MyObjectifyUtil.getField would throw the exception,
// I'm expecting an exception to be thrown just before that method call
exceptionGrabber.expect(MyObjectifyNoSuchFieldException.class);
MyObjectifyUtil.getField(DownloadTask.class, fieldName);
...
}
}
This approach better than the
#Test (expected=...) approach because #Test (expected=...) only
tests if the method execution halts by throwing the given exception,
not if the call you wanted to throw the exception threw one. For example, the test will succeed even if doSomething method threw the MyObjectifyNoSuchFieldException exception which may not be desirable
You get to test more than just the type of the exception being thrown. For example, you could check for a particular exception instance or exception message and so on
The try/catch block approach, because of readability and conciseness.
I couldn't implement it in a nice way if counting assertion error descriptions (probably this is why Hamcrest does not provide such a feature), but if you're playing well with Java 8 then you might want something like this (however I don't think it would be ever used because of the issues described below):
IThrowingRunnable
This interface is used to wrap code that could potentially throw exceptions. Callable<E> might be used as well, but the latter requires a value to be returned, so I think that a runnable ("void-callable") is more convenient.
#FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {
void run()
throws E;
}
FailsWithMatcher
This class implements a matcher that requires the given callback to throw an exception. A disadvantage of this implementation is that having a callback throwing an unexpected exception (or even not throwing a single) does not describe what's wrong and you'd see totally obscure error messages.
public final class FailsWithMatcher<EX extends Throwable>
extends TypeSafeMatcher<IThrowingRunnable<EX>> {
private final Matcher<? super EX> matcher;
private FailsWithMatcher(final Matcher<? super EX> matcher) {
this.matcher = matcher;
}
public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType) {
return new FailsWithMatcher<>(instanceOf(throwableType));
}
public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType, final Matcher<? super EX> throwableMatcher) {
return new FailsWithMatcher<>(allOf(instanceOf(throwableType), throwableMatcher));
}
#Override
protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
try {
runnable.run();
return false;
} catch ( final Throwable ex ) {
return matcher.matches(ex);
}
}
#Override
public void describeTo(final Description description) {
description.appendText("fails with ").appendDescriptionOf(matcher);
}
}
ExceptionMessageMatcher
This is a sample matcher to make a simple check for the thrown exception message.
public final class ExceptionMessageMatcher<EX extends Throwable>
extends TypeSafeMatcher<EX> {
private final Matcher<? super String> matcher;
private ExceptionMessageMatcher(final Matcher<String> matcher) {
this.matcher = matcher;
}
public static <EX extends Throwable> Matcher<EX> exceptionMessage(final String message) {
return new ExceptionMessageMatcher<>(is(message));
}
#Override
protected boolean matchesSafely(final EX ex) {
return matcher.matches(ex.getMessage());
}
#Override
public void describeTo(final Description description) {
description.appendDescriptionOf(matcher);
}
}
And the test sample itself
#Test
public void test() {
assertThat(() -> emptyList().get(0), failsWith(IndexOutOfBoundsException.class, exceptionMessage("Index: 0")));
assertThat(() -> emptyList().set(0, null), failsWith(UnsupportedOperationException.class));
}
Note that this approach:
... is test-runner-independent
... allows to specify multiple assertions in a single test
And the worst thing, a typical fail will look like
java.lang.AssertionError:
Expected: fails with (an instance of java.lang.IndexOutOfBoundsException and is "Index: 0001")
but: was <foo.bar.baz.FailsWithMatcherTest$$Lambda$1/127618319#6b143ee9>
Maybe using a custom implementation of the assertThat() method could fix it.
I suppose the cleanest way is to define a function like
public static Throwable exceptionOf(Callable<?> callable) {
try {
callable.call();
return null;
} catch (Throwable t) {
return t;
}
}
somewhere and then e.g. call
assertThat(exceptionOf(() -> callSomethingThatShouldThrow()),
instanceOf(TheExpectedException.class));
perhaps also using something like the ExceptionMessageMatcher of this answer.
Since junit 4.13 you can use its Assert.assertThrows, like this:
import static org.junit.Assert.assertThrows;
...
MyObjectifyNoSuchFieldException ex = assertThrows(MyObjectifyNoSuchFieldException.class, () -> MyObjectifyUtil.getField(DownloadTask.class, fieldName));
// now you can go further and assert things about the exception ex
// if MyObjectifyUtil.getField(...) does not throw exception, the test will fail right at assertThrows
In my opinion this sort of exceptions asserting is superior to #Test(expected=MyObjectifyNoSuchFieldException.class) because you can:
further assert things about the exception itself;
assert things about side effects (in your mocks, for example);
continue your test case.
You should use junit-utils, which does contain an ExceptionMatcher that can be used together with Hamcrest's assertThat() method.
Example 1:
assertThat(() -> MyObjectifyNoSuchFieldException.class,
throwsException(MyObjectifyNoSuchFieldException.class));
Example 2:
assertThat(() -> myObject.doStuff(null),
throwsException(MyObjectifyNoSuchFieldException.class)
.withMessageContaining("ERR-120008"));
Additional details here: obvj.net/junit-utils
In addition to the above.
if you change the interfaces to ... extends Exception, you can throw an Error like this:
#Override
protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
try {
runnable.run();
throw new Error("Did not throw Exception");
} catch (final Exception ex) {
return matcher.matches(ex);
}
}
trace will look like this:
java.lang.Error: Did not throw Exception
at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.java:31)
at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.java:1)
at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:12)
at org.junit.Assert.assertThat(Assert.java:956)
at org.junit.Assert.assertThat(Assert.java:923)
at
...
I have the following scenario:
public class ClassA {
public void methodA(){
try {
int result=methodB();
} catch (IOException e) {
//Some code here
}
}
private int methodB() throws IOException{
//Some code here
return 1;
}
}
I want to cover the catch block of the public method methodA() in my test. I don't want to change the visibility of the private method. Is there any way to achieve partial mock of private method using EasyMock? Or is there any way to change the behaviour of private method in my Junit class to throw exception without using mocking?
Thanks in advance.
You cannot do this with Easymock alone, you can do this with a combination of EasyMock and Powermock. Then you mock the return value of the private method.
To test the catch block, you must tell EasyMock to throw an exception when calling methodB.
From the docs:
For specifying exceptions (more exactly: Throwables) to be thrown, the object returned
by expectLastCall() and expect(T value) provides the method andThrow(Throwable throwable).
The method has to be called in record state after the call to the Mock Object for which
it specifies the Throwable to be thrown.
Example:
expectLastCall().andThrow(new IOException("Now to test catch block..."));
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.