Search code examples
javamockitopowermockpowermockito

PowerMock: mocking multiple calls of same method with different parameters behave abnormally


I am new to Mockito, and having an issue that took lot of my time. Below is my problem statement and executable code.

Problem

Whenever i try to mock multiple behaviours from same method with different arguments mockito/powermockito use the last behaviour which i defined for test in a single test.Below is my example, Service class has a static foo method that is being called from my method (that i want to test) number of times with different parameters.

It throws ClassCastException and want to cast BResponse into AResponse, because my last stubbing if for BResponse whereas my first call for foo in ClassUnderTest.execute() demands AResponse.

Sample Code

package poc.staticmethod;

import static org.mockito.Matchers.any;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import lombok.AllArgsConstructor;
import lombok.Data;

@RunWith(PowerMockRunner.class)
@PrepareForTest(PocStaticTest.Service.class)
public class PocStaticTest {
  

  @InjectMocks
  ClassUnderTest c = new ClassUnderTest();
  
  @Before
  public void beforeTest() throws Exception {
    mockStatic(Service.class);
  }

  @Test
  public void myTest() {
    when(Service.foo(any(), new ARequest(any(), "A"))).thenReturn(new AResponse(1, "passed"));
    when(Service.foo(any(), new ARequest(any(), "2A")))
        .thenReturn(new AResponse(2, "passed"));
    when(Service.foo(any(), new BRequest(any(), "B")))
        .thenReturn(new BResponse(112, "passed"));
    
    c.execute();
  }

  public class ClassUnderTest {
    public void execute() {
      AResponse ar = (AResponse) Service.foo("A1", new ARequest(1, "A"));
      AResponse ar2 = (AResponse) Service.foo("A2", new ARequest(2, "2A"));
      BResponse br = (BResponse) Service.foo("B1", new BRequest(1, "B"));
    }
  }
  
  public static class Service {
    public static Object foo(String firstArgument, Object obj) {
      return null;
    }
  }
  
  @Data
  @AllArgsConstructor
  public class ARequest {
    public Integer num;
    public String name;
  }

  @Data
  @AllArgsConstructor
  public class AResponse {
    public Integer error;
    public String message;
  }

  @Data
  @AllArgsConstructor
  public class BRequest {
    public Integer num;
    public String name;
  }

  @Data
  @AllArgsConstructor
  public class BResponse {
    public Integer error;
    public String message;
  }

}

Exception:

java.lang.ClassCastException: poc.staticmethod.PocStaticTest$BResponse cannot be cast to poc.staticmethod.PocStaticTest$AResponse
    at poc.staticmethod.PocStaticTest$ClassUnderTest.execute(PocStaticTest.java:44)
    at poc.staticmethod.PocStaticTest.myTest(PocStaticTest.java:39)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:316)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:300)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access$100(PowerMockJUnit47RunnerDelegateImpl.java:59)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:288)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:208)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:147)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:121)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:123)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)  

Solution

  • use eq() matcher for your custom object and by doing this change your function mocking would looks linke:

    when(Service.foo(any(), eq(new ARequest(1, "A")))).thenReturn(new AResponse(1, "passed"));
    when(Service.foo(any(), eq(new ARequest(2, "2A")))).thenReturn(new AResponse(2, "passed"));
    when(Service.foo(any(), eq(new BRequest(1, "B")))).thenReturn(new BResponse(112, "passed"));
    

    you should specify the arguments in your Request objects and remove any() from inside.

    alternative option is write your answer and check types inside it, like:

    when(mock.foo(anyString(), anyObject())).thenAnswer(
        invocation -> {
            Object argument = invocation.getArguments()[1];
            if (argument.equals(new ARequest(1, "A"))) {
                return new AResponse(1, "passed");
            } else if (argument.equals(new ARequest(2, "2A"))) {
                return new AResponse(2, "passed");
            } else if (argument.equals(new BRequest(1, "B"))) {
                return new BResponse(112, "passed");
            }
            throw new InvalidUseOfMatchersException(
                String.format("Argument %s does not match", argument)
            );
        }
    );