在 Java 中模擬 URL 連接
一、概述
UrlConnection
是一個抽像類,它提供了一個接口來處理 Web 上的資源,例如從 URL 檢索數據並向它們發送數據。
在編寫單元測試時,我們通常需要一種方法來模擬網絡連接和響應,而無需實際發出實際的網絡請求。
在本教程中,我們將研究在 Java 中模擬 URL 連接的幾種方法。
2. 一個簡單的 URL Fetcher 類
在本教程中,我們測試的重點將是一個簡單的 URL 獲取器類:
public class UrlFetcher {
private URL url;
public UrlFetcher(URL url) throws IOException {
this.url = url;
}
public boolean isUrlAvailable() throws IOException {
return getResponseCode() == HttpURLConnection.HTTP_OK;
}
private int getResponseCode() throws IOException {
HttpURLConnection con = (HttpURLConnection) this.url.openConnection();
return con.getResponseCode();
}
}
出於演示目的,我們有一個公共方法isUrlAvailable()
,它指示給定地址的 URL 是否可用。返回值基於我們收到的 HTTP 響應消息中的狀態代碼。
3. 使用純 Java 進行單元測試
通常,使用模擬的第一個停靠點是使用第三方測試框架。但是,在某些情況下,這可能不是一個可行的選擇。
幸運的是, URL
類提供了一種機制,允許我們提供知道如何建立連接的自定義處理程序。我們可以使用它來提供我們的處理程序,它將返回一個虛擬連接對象和響應。
3.1.支援班
對於這種方法,我們需要幾個支持類。讓我們從定義MockHttpURLConnection
開始:
public class MockHttpURLConnection extends HttpURLConnection {
protected MockHttpURLConnection(URL url) {
super(url);
}
@Override
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
@Override
public void disconnect() {
}
@Override
public boolean usingProxy() {
return false;
}
@Override
public void connect() throws IOException {
}
}
如我們所見,此類是一個簡單的擴展,具有HttpURLConnection
類的最小實現。這裡的重要部分是我們提供了一種設置和獲取 HTTP 響應代碼的機制。
接下來,我們需要一個模擬流處理程序來返回我們新創建的MockHttpURLConnection
:
public class MockURLStreamHandler extends URLStreamHandler {
private MockHttpURLConnection mockHttpURLConnection;
public MockURLStreamHandler(MockHttpURLConnection mockHttpURLConnection) {
this.mockHttpURLConnection = mockHttpURLConnection;
}
@Override
protected URLConnection openConnection(URL url) throws IOException {
return this.mockHttpURLConnection;
}
}
最後,我們需要提供一個流處理程序工廠,它將返回我們新創建的流處理程序:
public class MockURLStreamHandlerFactory implements URLStreamHandlerFactory {
private MockHttpURLConnection mockHttpURLConnection;
public MockURLStreamHandlerFactory(MockHttpURLConnection mockHttpURLConnection) {
this.mockHttpURLConnection = mockHttpURLConnection;
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return new MockURLStreamHandler(this.mockHttpURLConnection);
}
}
3.2.把它們放在一起
現在我們已經準備好支持類,我們可以繼續編寫我們的第一個單元測試:
private static MockHttpURLConnection mockHttpURLConnection;
@BeforeAll
public static void setUp() {
mockHttpURLConnection = new MockHttpURLConnection(null);
URL.setURLStreamHandlerFactory(new MockURLStreamHandlerFactory(mockHttpURLConnection));
}
@Test
void givenMockedUrl_whenRequestSent_thenIsUrlAvailableTrue() throws Exception {
mockHttpURLConnection.setResponseCode(HttpURLConnection.HTTP_OK);
URL url = new URL("https://www.baeldung.com/");
UrlFetcher fetcher = new UrlFetcher(url);
assertTrue(fetcher.isUrlAvailable(), "Url should be available: ");
}
讓我們來看看我們測試的關鍵部分:
- 首先,我們從定義
setUp()
方法開始,我們在其中創建MockHttpURLConnection
並通過靜態方法setURLStreamHandlerFactory()
將其註入到URL
類中。 - 現在我們可以開始編寫測試的主體了。首先,我們需要在 m
ockHttpURLConnection
變量上使用setResponseCode()
方法設置預期的響應代碼。 - 然後我們可以創建一個新的 URL 並在最終斷言
isUrlAvailable()
方法之前構造我們的UrlFetcher
當我們運行測試時,無論網址是否可用,我們都應該始終獲得相同的行為。確保這一點的一個很好的測試是關閉您的 Wi-Fi 或網絡連接,並檢查測試是否仍然以完全相同的方式運行。
3.3.這種方法的問題
雖然此解決方案有效且不依賴第三方庫,但由於多種原因它有點麻煩。
首先,我們需要創建幾個mock支持類,隨著我們的測試需求越來越複雜,我們的mock對像也會越來越複雜。例如,如果我們需要開始模擬不同的響應主體。
同樣,我們的測試有一些重要的設置,我們將靜態方法調用與URL
類的新實例混合在一起。這是令人困惑的,並且可能會導致進一步的意外結果。
4. 使用 Mockito
在下一節中,我們將了解如何使用著名的單元測試框架 Mockito 簡化測試。
首先,我們需要將[mockito](https://mvnrepository.com/artifact/org.mockito/mockito-core)
依賴項添加到我們的項目中:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
現在我們可以定義我們的測試:
@Test
void givenMockedUrl_whenRequestSent_thenIsUrlAvailableFalse() throws Exception {
HttpURLConnection mockHttpURLConnection = mock(HttpURLConnection.class);
when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
URL mockURL = mock(URL.class);
when(mockURL.openConnection()).thenReturn(mockHttpURLConnection);
UrlFetcher fetcher = new UrlFetcher(mockURL);
assertFalse(fetcher.isUrlAvailable(), "Url should be available: ");
}
這一次,我們使用 Mockito 的mock
方法創建一個模擬 URL 連接。然後,我們將模擬對象配置為在調用其openConnection
方法時返回模擬 HTTP URL 連接。當然,我們的模擬 HTTP 連接已經包含存根響應代碼。
我們應該注意,對於低於 4.8.0 的 Mockito 版本,我們在運行此測試時可能會收到錯誤消息:
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class java.net.URL
Mockito cannot mock/spy because :
- final class
發生這種情況是因為 URL 是最終類,並且在 Mockito 的早期版本中,不可能直接模擬最終類型和方法。
為了解決這個問題,我們可以簡單地向我們的pom.xml
添加一個額外的依賴項:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
現在我們的測試將成功運行!
5. 使用 JMockit
在我們的最後一個示例中,我們將查看另一個名為 JMockit 的測試庫以確保完整性。
首先,我們需要將[jmockit](https://mvnrepository.com/artifact/org.jmockit/jmockit)
依賴項添加到我們的項目中:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49</version>
</dependency>
現在我們可以繼續定義我們的測試類:
@ExtendWith(JMockitExtension.class)
class UrlFetcherJMockitUnitTest {
@Test
void givenMockedUrl_whenRequestSent_thenIsUrlAvailableTrue(@Mocked URL anyURL,
@Mocked HttpURLConnection mockConn) throws Exception {
new Expectations() {{
mockConn.getResponseCode();
result = HttpURLConnection.HTTP_OK;
}};
UrlFetcher fetcher = new UrlFetcher(new URL("https://www.baeldung.com/"));
assertTrue(fetcher.isUrlAvailable(), "Url should be available: ");
}
}
JMockit 的最強點之一是它的可表達性。為了創建模擬並定義它們的行為,而不是從模擬 API 調用方法,我們只需要直接定義它們。
六,結論
在本文中,我們了解了幾種可以模擬 URL 連接以編寫不依賴任何外部服務的獨立單元測試的方法。首先,我們查看了一個使用 vanilla Java 的示例,然後探討了使用 Mockito 和 JMockit 的其他兩個選項。
一如既往,本文的完整源代碼可在 GitHub 上獲得。