如何对调用API的货币兑换方法进行Mock测试?
要解决依赖外部API的测试问题,核心是解耦网络请求逻辑,再用Mock技术模拟API返回结果,脱离真实API环境稳定测试业务逻辑。下面是具体步骤和代码示例:
第一步:重构原代码,解耦网络依赖
原代码直接在exchange方法里创建URL和HttpURLConnection,导致无法Mock网络请求。我们需要把网络请求逻辑抽成独立接口,让CurrencyService依赖接口而非具体实现。
1. 定义网络请求接口
public interface HttpHandler { String sendGetRequest(String url, Map<String, String> headers) throws IOException; }
2. 实现真实网络请求逻辑
public class RealHttpHandler implements HttpHandler { @Override public String sendGetRequest(String url, Map<String, String> headers) throws IOException { HttpURLConnection conn = null; BufferedReader reader = null; StringBuilder responseContent = new StringBuilder(); try { URL requestUrl = new URL(url); conn = (HttpURLConnection) requestUrl.openConnection(); conn.setRequestMethod("GET"); // 设置请求头 if (headers != null) { for (Map.Entry<String, String> entry : headers.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } } int status = conn.getResponseCode(); InputStream inputStream = status == 200 ? conn.getInputStream() : conn.getErrorStream(); reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { responseContent.append(line); } } finally { if (reader != null) { reader.close(); } if (conn != null) { conn.disconnect(); } } return responseContent.toString(); } }
3. 修改CurrencyService,注入HttpHandler
public class CurrencyService { private final Pattern currencyPattern = Pattern.compile("[A-Z]{3}"); private final HttpHandler httpHandler; private final String apiBaseUrl = "https://api.apilayer.com/exchangerates_data/"; private final String apiKey = "tSaLWidFRzgzO2mGNfFgVEIr2cqeWCUY"; // 构造方法注入依赖,测试时可替换为Mock对象 public CurrencyService(HttpHandler httpHandler) { this.httpHandler = httpHandler; } public double exchange(String currencyFrom, String currencyTo, double amount) { // 验证货币格式 if (!currencyPattern.matcher(currencyFrom).matches() || !currencyPattern.matcher(currencyTo).matches()) { throw new BadPatternForCurrency("Currency FROM and TO must be 3-letter uppercase format (e.g. USD, EUR)"); } // 验证金额 if (amount < 0) { throw new NoMinusValuesInAmountException("Amount must be greater than 0!"); } // 构建请求URL String requestUrl = String.format("%sconvert?to=%s&from=%s&amount=%s", apiBaseUrl, currencyTo.toUpperCase(), currencyFrom.toUpperCase(), amount); // 设置请求头 Map<String, String> headers = new HashMap<>(); headers.put("apikey", apiKey); try { // 调用HttpHandler发送请求 String response = httpHandler.sendGetRequest(requestUrl, headers); JSONObject obj = new JSONObject(response); return obj.getDouble("result"); } catch (IOException e) { throw new RuntimeException("Failed to fetch exchange rate", e); } } }
第二步:用Mockito编写测试用例
现在可以用Mockito MockHttpHandler接口,模拟不同API响应,测试exchange方法的各种场景。
1. 添加依赖(Maven示例)
在pom.xml中加入JUnit和Mockito依赖:
<dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20230227</version> </dependency> </dependencies>
2. 编写测试类
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class CurrencyServiceTest { @Mock private HttpHandler mockHttpHandler; // Mock的HttpHandler对象 @InjectMocks private CurrencyService currencyService; // 自动注入Mock对象到CurrencyService @Test public void exchange_ValidParameters_ReturnsCorrectResult() throws IOException { // 模拟API返回的JSON响应 String mockResponse = "{\"result\": 92.5}"; // 指定调用sendGetRequest时返回模拟响应 when(mockHttpHandler.sendGetRequest(anyString(), any(Map.class))).thenReturn(mockResponse); // 调用待测试方法 double result = currencyService.exchange("USD", "JPY", 1); // 验证结果是否符合预期 assertEquals(92.5, result, 0.01); // 验证HttpHandler方法是否被调用一次 verify(mockHttpHandler, times(1)).sendGetRequest(anyString(), any(Map.class)); } @Test public void exchange_InvalidCurrencyFormat_ThrowsBadPatternException() { // 测试货币格式错误场景 assertThrows(BadPatternForCurrency.class, () -> { currencyService.exchange("US", "JPY", 1); }); assertThrows(BadPatternForCurrency.class, () -> { currencyService.exchange("USD", "JP", 1); }); } @Test public void exchange_NegativeAmount_ThrowsNoMinusValuesException() { // 测试金额为负场景 assertThrows(NoMinusValuesInAmountException.class, () -> { currencyService.exchange("USD", "JPY", -100); }); } @Test public void exchange_ApiReturnsError_ThrowsRuntimeException() throws IOException { // 模拟API连接失败 when(mockHttpHandler.sendGetRequest(anyString(), any(Map.class))).thenThrow(new IOException("API connection failed")); // 验证是否抛出异常 assertThrows(RuntimeException.class, () -> { currencyService.exchange("USD", "JPY", 1); }); } }
关键说明
- 解耦的意义:把网络请求抽成接口后,测试时可完全替换为Mock实现,无需依赖真实API,避免汇率变动和网络不稳定的影响。
- Mock的作用:通过Mockito的
when方法,可指定方法调用时返回的结果或抛出的异常,精准测试各种业务场景。 - 测试覆盖:用例覆盖了正常流程、参数校验失败、API异常三种核心场景,确保
exchange方法逻辑正确。
内容的提问来源于stack exchange,提问作者Gohan255
相关产品推荐
相关产品推荐

