I am trying to mock out a private method that is making a JNDI call. When that method gets called from a unit test, it throws an exception^. I would like to mock-out that method for testing purposes. I used the sample code from another questions answer, and while the test passes, it seems that the underlying method still gets called. I inserted a System.err.println()
in the doTheGamble()
method, and it gets printed out to my console.
Interesting enough, if I comment out the first assertThat
, the test passes. ?:(
So, how do I mock out a private method so that it does not get called?
import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.support.membermodification.MemberMatcher.method; import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(CodeWithPrivateMethod.class) public class PowerMock_Test { static boolean gambleCalled = false; @Test(expected = RuntimeException.class) public void when_gambling_is_true_then_always_explode() throws Exception { CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod()); when(spy, method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class)) .withArguments(anyString(), anyInt()) .thenReturn(true); /* 1 */ assertThat( PowerMock_Test.gambleCalled, is(false) ); spy.meaningfulPublicApi(); /* 2 */ assertThat( PowerMock_Test.gambleCalled, is(false) ); } } class CodeWithPrivateMethod { public void meaningfulPublicApi() { if (doTheGamble("Whatever", 1 << 3)) { throw new RuntimeException("boom"); } } private boolean doTheGamble(String whatever, int binary) { Random random = new Random(System.nanoTime()); boolean gamble = random.nextBoolean(); System.err.println( "\n>>> GAMBLE CALLED <<<\n" ); PowerMock_Test.gambleCalled = true; return gamble; } }
^ understandably, since my workspace does not support JNDI, only the production environment does
% I am using the latest versions of all the library, JUnit 4.10, Mockito 1.8.5, Hamcrest 1.1, Javassist 3.15.0, and PowerMock 1.4.10.
Of course you can – and probably will – use Mockito and PowerMock in the same JUnit test at some point of time.
For Mockito, there is no direct support to mock private and static methods. In order to test private methods, you will need to refactor the code to change the access to protected (or package) and you will have to avoid static/final methods.
PowerMock is an open-source Java framework used for creating a mock object in unit testing. It extends other mocking frameworks such as EasyMock and Mockito to enhance the capabilities.
From the PowerMock Private Method Example:
@RunWith(PowerMockRunner.class) // We prepare PartialMockClass for test because it's final or we need to mock private or static methods @PrepareForTest(PartialMockClass.class) public class YourTestCase { @Test public void privatePartialMockingWithPowerMock() { PartialMockClass classUnderTest = PowerMockito.spy(new PartialMockClass()); // use PowerMockito to set up your expectation PowerMockito.doReturn(value).when(classUnderTest, "methodToMock", "parameter1"); // execute your test classUnderTest.execute(); // Use PowerMockito.verify() to verify result PowerMockito.verifyPrivate(classUnderTest, times(2)).invoke("methodToMock", "parameter1"); }
So to apply this to your code, I think it might become:
@RunWith(PowerMockRunner.class) @PrepareForTest(CodeWithPrivateMethod.class) public class PowerMock_Test { @Test(expected = RuntimeException.class) public void when_gambling_is_true_then_always_explode() throws Exception { CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod()); PowerMockito.doReturn(true).when(spy, "doTheGamble", anyString(), anyInt()); /* 1 */ PowerMockito.verifyPrivate(spy, times(0)).invoke("doTheGamble", anyString(), anyInt()); spy.meaningfulPublicApi(); /* 2 */ PowerMockito.verifyPrivate(spy, times(2)).invoke("doTheGamble", anyString(), anyInt()); } }
I just coded that in the editor here. No tests have actually been run, and no bugs have been harmed in the crafting of this code.
ArtB,
Just pasting the complete code which works fine in my Eclipse IDE. I have only changed the expectation i said in my last post. Good luck.
import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.powermock.api.support.membermodification.MemberMatcher.method; import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(CodeWithPrivateMethod.class) public class PowerMock_Test { static boolean gambleCalled = false; @Test(expected = RuntimeException.class) public void when_gambling_is_true_then_always_explode() throws Exception { CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod()); // PowerMockito.doReturn(true).when(spy, "doTheGamble", anyString(), anyInt()); PowerMockito.doReturn(true).when(spy, method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class)) .withArguments(anyString(), anyInt()); assertThat( PowerMock_Test.gambleCalled, is(false) ); spy.meaningfulPublicApi(); assertThat( PowerMock_Test.gambleCalled, is(false) ); } } class CodeWithPrivateMethod { public void meaningfulPublicApi() { if (doTheGamble("Whatever", 1 << 3)) { throw new RuntimeException("boom"); } } private boolean doTheGamble(String whatever, int binary) { Random random = new Random(System.nanoTime()); boolean gamble = random.nextBoolean(); System.err.println( "\n>>> GAMBLE CALLED <<<\n" ); PowerMock_Test.gambleCalled = true; return gamble; } }
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