使用PowerMockito/Mockito无法模拟URL类

8

我正在尝试使用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语句来注入mock。无论我尝试什么,它都从未拦截构造函数。我想要做的就是“捕获”对openConnection()的调用,并将其返回我的模拟HTTP连接而不是真实的连接。
在这个脚本中,我已经模拟了其他类,并且它们按预期工作。也许我需要第二个人的帮助(很可能是真的),或者URL类有一些独特的东西。我注意到,如果我使用"whenNew(URL.class).withAnyArguments()"并将"thenReturn"更改为"thenAnswer",我可以触发它。唯一的问题是我从未收到过我的代码的URL调用。我看到的是调用URL.class的三参数构造函数,其中所有参数都为null。难道这个类来自Java运行时,并由测试运行器引导?非常感谢您的帮助。

你不需要使用Powermock来测试这个。只需模拟字符串url,例如使用基于文件的路径(“file://my-test.properties”)。这样就可以工作了,并且您可以传递所需的数据。 - Jérémie B
@JérémieB,您能详细解释一下吗?这似乎是一个优雅而简单的解决方案,但被测试的代码期望得到一个HttpURLConnection,如果我将URL字符串更改为引用文件,则会出现ClassCastException(即java.lang.ClassCastException:sun.net.www.protocol.ftp.FtpURLConnection无法转换为java.net.HttpURLConnection)。此外,您如何使用这种方法验证请求的输入参数? - Jim
你不应该直接使用 HttpURLConnection,而是使用 URLURLConnection。在 HttpURLConnection 中没有任何有用的东西。URL 的目的是抽象传输层。 - Jérémie B
3个回答

1

当使用PowerMockito.whenNew时,常见的错误是忽略了准备新实例的类。

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

来自Powermock wiki

因此,您需要稍微更改测试,并向@PrepareForTest添加一个创建URL新实例的类,如下所示:

@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 wlInvokeUrl = new URL(wlInvokeUrlString); 这一行总是将wlInvokeUrl分配为null。我通过重构代码解决了原始问题,但我仍然致力于看到PowerMockito在这方面发挥作用,以防您有其他建议。 - Jim
你们使用的 PowerMock、Mockito、Java 版本是什么?我已经测试了最新版本和 Mockito 1.10.19,并且只有在我通过 null 替换 mockedUrl 的情况下才能重现你们的问题。像这样:PowerMockito.whenNew(URL.class).withArguments("MyURLString").thenReturn(null); 在所有其他情况下,我的测试版本都能通过。顺便说一下,我使用 Java 8 运行测试。 - Arthur Zagretdinov
你能否在你的环境中尝试运行我的版本? - Arthur Zagretdinov

0
我不确定 .withParameterTypes(x).withArguments(x) 之间的区别,但我相信你需要按照以下方式设置才能让你的代码正常工作。试一下,如果还有问题请告诉我。
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
PowerMockito.whenNew(URL.class).withArguments(Mockito.anyString()).thenReturn(mockedURL);

我尝试了这些代码,但是看到的结果都一样。与其使用模拟对象,我创建并使用了真实的URL和HttpURLConnection类。问题可能出在URL类被声明为final的事实上吗?根据我所读的,PowerMockito应该能够处理这个问题。 - Jim

0
问题在于真实调用的参数与您的模拟期望不匹配。 PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL) 只有当调用为 new URL("MyURLString") 时才会返回 mockedURL
如果您将其更改为:
PowerMockito.whenNew( URL.class ).withParameterTypes( String.class )
    .withArguments( org.mockito.Matchers.any( String.class ) ).thenReturn( mockedURL );

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


当你尝试将 "thenReturn" 更改为 "thenAnswer" 并使用 "whenNew(URL.class).withAnyArguments()" 触发时,我可以得到它的触发。唯一的问题是我从我的代码中永远不会得到 URL 调用。我看到的是对 URL.class 的 3 个参数构造函数的调用,其中所有参数都为 null。

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


在尝试让这个工作的过程中,我尝试了很多方法,包括对您的建议进行变化。我按照您提到的更改whenNew行的方式重新测试,但仍然得到相同的结果。您还有其他想法吗? - Jim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接