【问题标题】:Unable to mock URL class using PowerMockito/Mockito无法使用 PowerMockito/Mockito 模拟 URL 类
【发布时间】:2016-02-19 18:12:52
【问题描述】:

我正在尝试使用 PowerMockito 在我正在测试的代码中模拟 java.net.URL 类的创建。基本上,我想阻止真正的 HTTP 请求发生,而是 1)在发出请求时检查数据,2)在模拟响应中提供我自己的测试数据。这就是我正在尝试的:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class, MockedHttpConnection.class })
public class Test {
    URL mockedURL = PowerMockito.mock(URL.class);
    MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);

...
    PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);

...
}

我要测试的代码如下所示:

URL wlInvokeUrl = new URL(wlInvokeUrlString);
connection = (HttpURLConnection) wlInvokeUrl.openConnection();

在我的测试场景的早期,我模拟了 wlInvokeUrlString 以匹配“MyURLString”。我也尝试过使用其他各种形式的 whenNew 线,试图注入模拟。无论我尝试什么,它都不会拦截构造函数。我要做的就是“捕捉”对 openConnection() 的调用,并让它返回我的模拟 HTTP 连接,而不是真实的。

我已经在同一个脚本中模拟了这个类之前的其他类,这些类都按预期工作。要么我需要第二双眼睛(可能是真的),要么 URL 类有一些独特之处。我确实注意到,如果我使用“whenNew(URL.class).withAnyArguments()”并将“thenReturn”更改为“thenAnswer”,我可以触发它。唯一的问题是我从来没有收到我的代码的 URL 调用。我看到的是对 URL.class 的 3 参数构造函数的调用,其中所有参数都为空。难道这个类来自 Java 运行时并由测试运行程序引导?非常感谢任何帮助。

【问题讨论】:

  • 你不需要 Powermock 来测试这个。只需模拟字符串 url,例如使用基于文件的路径(“file://my-test.properties”)。这会起作用,你可以传递你需要的数据
  • @JérémieB 你能详细说明一下吗?这似乎是一个优雅而简单的解决方案,但被测代码需要一个 HttpURLConnection,如果我更改 URL 字符串以引用文件(即 java.lang.ClassCastException: sun.net.www.protocol. ftp.FtpURLConnection 不能转换为 java.net.HttpURLConnection)。另外,您将如何使用这种方法验证请求中的输入参数?
  • 你不应该直接使用HttpURLConnection,而是URLURLConnectionHttpURLConnection 没有什么有用的。 URL 的目的是抽象传输层。

标签: junit mocking mockito powermock powermockito


【解决方案1】:

使用PowerMockito.whenNew 时常犯的错误。

请注意,您必须准备创建 MyClass 的新实例以进行测试的类,而不是 MyClass 本身。例如。如果执行 new MyClass() 的类称为 X,那么您必须执行 @PrepareForTest(X.class) 才能使 whenNew 工作

来自Powermock wiki

所以,你需要稍微改变一下你的测试并添加到@PrepareForTesta 类,它会创建一个URLlike 的新实例:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class, MockedHttpConnection.class , ConnectionUser.class})
public class URLTest {
public class URLTest {

    private ConnectionUser connectionUser;

    @Before
    public void setUp() throws Exception {

        connectionUser = new ConnectionUser();
    }

    @Test
    public void testName() throws Exception {

        URL mockedURL = PowerMockito.mock(URL.class);
        MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);

        PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
        PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);

        connectionUser.open();

        assertEquals(mockedConnection, connectionUser.getConnection());


    }
}

在哪里:

public class ConnectionUser {

    private String wlInvokeUrlString = "MyURLString";
    private HttpURLConnection connection;

    public void open() throws IOException {
        URL wlInvokeUrl = new URL(wlInvokeUrlString);
        connection = (HttpURLConnection) wlInvokeUrl.openConnection();
    }

    public HttpURLConnection getConnection() {
        return connection;
    }
}

【讨论】:

  • 您的答案最接近工作。进行这些更改后,将调用模拟 URL 构造函数。但是,它总是返回一个空对象。我的意思是,URL wlInvokeUrl = new URL(wlInvokeUrlString); 行总是将 wlInvokeUrl 分配给 null。我已经通过重构我的代码解决了最初的问题,但是如果您有任何其他建议,我仍然致力于看到 PowerMockito 可以解决这个问题。
  • 您使用哪些版本的 PowerMock、Mockito、Java?我使用最新版本和 Mockito 1.10.19 进行了测试,只有在我替换 mockedUrlvia null 的情况下才能重现您的问题。像这样:PowerMockito.whenNew(URL.class).withArguments("MyURLString").thenReturn(null); 在所有其他情况下,我的测试版本都通过了。顺便说一句,我使用 Java 8 运行测试。
  • 您可以尝试在您的环境中运行我的版本吗?
【解决方案2】:

我不确定.withParameterTypes(x).withArguments(x) 之间的区别,但我相信您需要按如下方式进行设置才能使您的代码正常工作。试一试,如果这没有帮助,请告诉我。

PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
PowerMockito.whenNew(URL.class).withArguments(Mockito.anyString()).thenReturn(mockedURL);

【讨论】:

  • 我尝试了这些行,但我看到了相同的结果。不是使用模拟对象,而是创建和使用真实的 URL 和 HttpURLConnection 类。问题可能出在 URL 类被声明为 final 的事实上吗?从我读过的内容来看,PowerMockito 应该能够处理这个问题。
【解决方案3】:

问题是实际调用的参数与模拟中的预期不匹配。

PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL) 将返回 mockedURL 只有调用是 new URL("MyURLString")

如果你把它改成:

PowerMockito.whenNew( URL.class ).withParameterTypes( String.class )
    .withArguments( org.mockito.Matchers.any( String.class ) ).thenReturn( mockedURL );

它将捕获传递给构造函数URL(String)(甚至null)的任何字符串并返回您的模拟。


当你尝试过

"whenNew(URL.class).withAnyArguments()" 并将 "thenReturn" 更改为 “thenAnswer” 我可以让它触发。唯一的问题是我从来没有得到 我的代码的 URL 调用。我看到的是调用 URL.class 的 3 参数构造函数,其中 参数。

PowerMock 将尝试模拟 所有 构造函数(org.powermock.api.mockito.internal.expectation.DelegatingToConstructorsOngoingStubbing.InvokeStubMethod 在第 122 行),然后它调用第一个(带有 3 个参数)并模拟它的答案。但是随后的调用将返回已经模拟的那个,因为你告诉它模拟任何参数。这就是为什么您在Answer 中只看到一个带有null, null, null 的电话。

【讨论】:

  • 在尝试完成这项工作的过程中,我尝试了很多方法,包括对您的建议进行修改。正如您提到的,我通过更改 whenNew 行重新测试,但仍然得到相同的结果。还有其他想法吗?
猜你喜欢
  • 2020-05-23
  • 1970-01-01
  • 2018-09-05
  • 1970-01-01
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-27
相关资源
最近更新 更多