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

如何用Mockito Stub内部调用的包级保护网络方法?

问题描述

我有一个AddressSearcher类,接收多个对象作为参数,可以创建真实依赖和真实被测对象。类里有个包级保护方法getDomainFromIp,负责执行网络调用,会被checkAddress方法内部调用。我想用Mockito只Stub这个方法,但常规的Mockito Spy不生效:直接调用Stub后的getDomainFromIp符合预期,但通过checkAddress调用时还是执行真实方法。我能用JMockit轻松实现,但不能修改代码把这个网络方法改成可注入的依赖。

被测类代码

public class AddressSearcher {
    private final AddressSearcherSettings settings;
    private final AddressList addressList;
    private final OtherDependency otherDependency;

    public AddressSearcher(
        final AddressSearcherSettings settings,
        final AddressList addressList,
        final OtherDependency otherDependency
    ) {
        this.settings = settings;
        this.addressList = addressList;
        this.otherDependency = otherDependency;
    }

    public Domain checkAddress(String address) {
        if (addressList.contains(address)) {
            return addressList.get(address);
        }

        Domain domain = this.getDomainFromIp(address);
        if (domain.isNotEmpty()) {
            return domain;
        }

        return EmptyDomain();

    }

    Domain getDomainFromIp(String address) {
        // 执行网络调用,根据IP查询域名
    }
}

测试代码(常规Spy失效)

@Test
public void checkAddressTest() {
    // 初始化真实依赖
    AddressSearcherSettings realSettings = ...;
    AddressList realAddressList = ...;
    OtherDependency realOtherDependency = ...;

    AddressSearcher realAddressSearcher = new AddressSearcher(realSettings, realAddressList, realOtherDependency);
    AddressSearcher spiedAddressSearcher = Mockito.spy(realAddressSearcher);

    // 直接调用Stub方法符合预期
    Mockito.doReturn(new Domain("Google")).when(this.spiedAddressSearcher).getDomainFromIp("8.8.8.8");
    assertEquals(new Domain("Google"), this.spiedAddressSearcher.getDomainFromIp("8.8.8.8"));

    // 但通过checkAddress调用时,仍执行真实方法,返回EmptyDomain
    assertEquals(new Domain("Google"), this.spiedAddressSearcher.checkAddress("8.8.8.8"));
}
问题原因

常规Mockito Spy失效的核心原因是:Mockito Spy基于动态代理实现,当你调用spiedAddressSearcher.checkAddress时,代理会转发调用到原始AddressSearcher实例的checkAddress方法。而原始实例内部的this.getDomainFromIp调用的是自身的方法,不会经过代理对象,因此Stub逻辑完全不会被触发。

解决方案

方案一:使用Mockito Mock对象+反射注入真实依赖

创建AddressSearcher的Mock对象,指定checkAddress调用真实逻辑,再通过反射(或Mockito的Whitebox工具)将真实依赖注入到Mock对象的私有字段中,最后StubgetDomainFromIp方法。此时checkAddress内部的this指向Mock对象,调用getDomainFromIp会触发Stub逻辑。

@Test
public void checkAddressTest() throws NoSuchFieldException, IllegalAccessException {
    // 初始化真实依赖
    AddressSearcherSettings realSettings = new AddressSearcherSettings();
    AddressList realAddressList = new AddressList();
    OtherDependency realOtherDependency = new OtherDependency();

    // 创建Mock对象
    AddressSearcher mockSearcher = Mockito.mock(AddressSearcher.class);

    // 让checkAddress调用真实业务逻辑
    Mockito.when(mockSearcher.checkAddress(Mockito.anyString())).thenCallRealMethod();

    // 反射注入真实依赖到Mock对象的私有字段
    Field settingsField = AddressSearcher.class.getDeclaredField("settings");
    settingsField.setAccessible(true);
    settingsField.set(mockSearcher, realSettings);

    Field addressListField = AddressSearcher.class.getDeclaredField("addressList");
    addressListField.setAccessible(true);
    addressListField.set(mockSearcher, realAddressList);

    Field otherDependencyField = AddressSearcher.class.getDeclaredField("otherDependency");
    otherDependencyField.setAccessible(true);
    otherDependencyField.set(mockSearcher, realOtherDependency);

    // Stub getDomainFromIp方法
    Mockito.doReturn(new Domain("Google")).when(mockSearcher).getDomainFromIp("8.8.8.8");

    // 执行测试,此时会返回Stub的Domain
    assertEquals(new Domain("Google"), mockSearcher.checkAddress("8.8.8.8"));
}

方案二:使用PowerMockito拦截内部方法调用

PowerMockito通过字节码修改技术,可以直接拦截原始对象的内部方法调用,无需依赖代理对象,完美解决常规Spy的局限性。

测试代码

@RunWith(PowerMockRunner.class)
@PrepareForTest(AddressSearcher.class) // 指定需要修改字节码的类
public class AddressSearcherTest {
    @Test
    public void checkAddressTest() {
        // 初始化真实依赖与真实实例
        AddressSearcherSettings realSettings = new AddressSearcherSettings();
        AddressList realAddressList = new AddressList();
        OtherDependency realOtherDependency = new OtherDependency();
        AddressSearcher realSearcher = new AddressSearcher(realSettings, realAddressList, realOtherDependency);

        // 创建PowerMock Spy
        PowerMockito.spy(realSearcher);

        // Stub内部调用的getDomainFromIp方法
        PowerMockito.doReturn(new Domain("Google")).when(realSearcher, "getDomainFromIp", "8.8.8.8");

        // 执行测试,此时checkAddress内部的getDomainFromIp会触发Stub逻辑
        assertEquals(new Domain("Google"), realSearcher.checkAddress("8.8.8.8"));
    }
}

内容的提问来源于stack exchange,提问作者candelphi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:39:24