PowerMock:使用不同参数模拟多个相同方法的调用表现异常

时间:2017-01-18 16:14:53

标签: java mockito powermock powermockito

我是Mockito的新手,并且遇到了花费我很多时间的问题。下面是我的问题陈述和可执行代码。

问题

每当我尝试使用不同参数从同一方法模拟多个行为时,mockito / powermockito使用我在单个测试中为测试定义的最后一个行为。下面是我的示例,Service类具有静态{{1使用不同参数从我的方法(我想测试)调用的方法。

它会抛出foo,并希望将ClassCastException投射到BResponse,因为我的最后一次存根是AResponse,而我BResponse的第一次调用是foo 1}}要求ClassUnderTest.execute()

示例代码

AResponse

例外:

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;
  }

}

2 个答案:

答案 0 :(得分:3)

对您的自定义对象使用 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")); 匹配器,通过执行此更改,您的函数模拟将看起来像linke:

Request

您应在any()个对象中指定参数并从内部删除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) ); } );

替代选项是写下你的答案并检查其中的类型,例如:

    View Source=

      <label for="speed">Select a speed</label>
        <select name="speed" id="speed">
          <option>Slower</option>
          <option>Slow</option>
          <option selected="selected">Medium</option>
          <option>Fast</option>
          <option>Faster</option>
        </select>

Runtime "Inspect element" you get:

<div class="ui-selectmenu-menu ui-front ui-selectmenu-open" style="top: 94.5938px; left: 22px;">
    <ul aria-hidden="false" aria-labelledby="speed-button" id="speed-menu" role="listbox" tabindex="0" 
        class="ui-menu ui-corner-bottom ui-widget ui-widget-content" aria-activedescendant="ui-id-26" aria-disabled="false" style="width: 256px;">
        <li class="ui-menu-item">
          <div id="ui-id-26" tabindex="-1" role="option" class="ui-menu-item-wrapper ui-state-active">Slower</div>
        </li>
        <li class="ui-menu-item">
          <div id="ui-id-27" tabindex="-1" role="option" class="ui-menu-item-wrapper">Slow</div>
        </li>
        <li class="ui-menu-item"><div id="ui-id-28" tabindex="-1" role="option" class="ui-menu-item-wrapper">Medium</div></li>
        <li class="ui-menu-item"><div id="ui-id-29" tabindex="-1" role="option" class="ui-menu-item-wrapper">Fast</div>
        </li><li class="ui-menu-item"><div id="ui-id-30" tabindex="-1" role="option" class="ui-menu-item-wrapper">Faster</div>
        </li>
    </ul>
</div>

答案 1 :(得分:1)

猜测:我认为你对这些嘲弄规范有错误的理解。

您写道:

when(Service.foo(any(), new ARequest(any(), "A")))

但这真的有意义吗?你想说:当调用foo()时,第一个参数无关紧要。第二个必须是一些ARequest对象。更具体地说,您想要说:ARequest对象在与其他人比较时忽略其“数字”部分。但是:这里的事情会如何发展。你必须在那里提供一个真正的对象,而不是“spec'ed”一个!

我的意思是:稍后,在运行时,模拟框架会看到foo()被调用,然后它开始寻找匹配的参数。但是,模拟框架应该如何理解您想要匹配包含任意数字的ARequest对象?

换句话说:我假设Lombok @Data将生成equals方法。当然,这些将比较这些类中的所有元素。

所以,我的解决方案可能是使用特定的请求对象,比如

when(Service.foo(any(), new ARequest(1, "A")))

然后确保数字ID始终为1。

或者,您可以尝试生成仅比较请求的“名称”部分的equals()方法。

长话短说:我认为你正在获得这些规范错误的语义。

我认为会发生的事情是new ARequest(any(), "A")只创建一些种ARequest对象,然后PowerMockito会尝试等同于此;而且结果可能一直都是“假的”。

最后:我希望你明白你混合两个字节码操作框架有一定的机会导致奇怪的问题?我见过很多次PowerMock(ito)导致奇怪的失败(因为它在某些方面改变了你的字节码);然后你添加lombok的东西?你甚至打算嘲笑静态电话?因此,我的个人建议:看看是否有机会将静态方法转换为某种实例方法(您甚至可以将自己的包装器放在静态调用中),这样您就可以使用普通的Mockito而不是PowerMockito在这里!