You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 22:15:36