Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocked private method with PowerMock, but underlying method still gets called

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.

like image 660
Sled Avatar asked Nov 08 '11 21:11

Sled


People also ask

Can we use both Mockito and PowerMock together?

Of course you can – and probably will – use Mockito and PowerMock in the same JUnit test at some point of time.

Can private methods be mocked?

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.

Is PowerMock a mocking framework?

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.


2 Answers

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.

like image 107
Mike Avatar answered Oct 08 '22 13:10

Mike


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;     } }    
like image 40
skusunam Avatar answered Oct 08 '22 14:10

skusunam