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

JUnit测试中如何通过反射设置PagingToolbar的I18nUtils属性

解决PagingToolbar构造函数依赖I18nUtils的JUnit测试问题

看起来你卡在了测试resetToolbar方法时的空指针问题上——因为PagingToolbar的构造函数直接依赖未初始化的I18nUtils,而且你还必须用new关键字创建这个实例。下面给你几个可行的解决方案:

方案1:用PowerMock拦截new操作(推荐,适合无法修改源码的场景)

PowerMock能拦截类的实例化过程,让我们替换成预先配置好的Mock实例,完美适配你必须new PagingToolbar()的需求。步骤如下:

  1. 先给测试类添加PowerMock的必要注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class) // 告诉PowerMock要处理A类里的new操作
public class ATest {
  1. 在测试方法中MockI18nUtils,并拦截PagingToolbar的实例化:
@Test
public void resetToolbar() throws Exception {
    // 1. Mock I18nUtils并设置预期返回值
    I18nUtils mockI18n = Mockito.mock(I18nUtils.class);
    when(mockI18n.getText(any(PagingToolbar.class), eq("facebook")))
        .thenReturn("Displaying items..."); // 模拟国际化文本返回

    // 2. 创建PagingToolbar实例并通过反射设置私有i18n字段
    PagingToolbar preparedToolbar = new PagingToolbar();
    Whitebox.setInternalState(preparedToolbar, "i18n", mockI18n); // PowerMock的Whitebox工具直接操作私有字段

    // 3. 拦截A类中的new PagingToolbar()调用,返回我们准备好的实例
    whenNew(PagingToolbar.class).withNoArguments().thenReturn(preparedToolbar);

    // 4. 初始化测试依赖并执行方法
    ListSelectionModel listSelectModelMock = Mockito.mock(ListSelectionModel.class);
    A tt = new A();
    tt.resetToolbar(listSelectModelMock);

    // 5. 添加你的验证逻辑(比如确认SelectionModel是否被设置)
    // verify(gridPanel).setSelectionModel(listSelectModelMock);
}

这样resetToolbar里的new PagingToolbar()会被替换成我们预先设置好i18n的实例,构造函数调用i18n.getText()时就不会抛出空指针了。

方案2:直接用Java反射操作私有字段(轻量但有局限性)

如果你不想引入PowerMock,也可以用Java原生反射API,但要注意:因为PagingToolbar的构造函数会立即调用i18n.getText(),所以直接new会先触发空指针,得用反射跳过构造函数创建实例:

@Test
public void resetToolbar() throws Exception {
    I18nUtils mockI18n = Mockito.mock(I18nUtils.class);
    when(mockI18n.getText(any(PagingToolbar.class), eq("facebook")))
        .thenReturn("Displaying items...");

    // 用反射创建PagingToolbar实例,不执行构造函数
    Constructor<PagingToolbar> constructor = PagingToolbar.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    PagingToolbar toolbar = constructor.newInstance();

    // 设置私有i18n字段
    Field i18nField = PagingToolbar.class.getDeclaredField("i18n");
    i18nField.setAccessible(true);
    i18nField.set(toolbar, mockI18n);

    // 这里需要把这个实例注入到A类的toolbar字段中(如果A的toolbar是私有,还要再用反射修改)
    Field toolbarField = A.class.getDeclaredField("toolbar");
    toolbarField.setAccessible(true);
    toolbarField.set(tt, toolbar);
}

⚠️ 注意:这个方案需要你在resetToolbar执行后替换A类的toolbar字段,或者调整执行顺序,实用性不如方案1,只适合临时应急。

方案3:修改源码(最佳实践,若允许调整代码)

如果能修改业务代码,最优雅的方式是给PagingToolbar添加带参数的构造函数,通过依赖注入传入I18nUtils:

class PagingToolbar {
    private I18nUtils i18n;

    // 新增带参数的构造函数,用于测试和依赖注入
    public PagingToolbar(I18nUtils i18n) {
        super();
        this.i18n = i18n;
        setDisplayingItemsText(i18n.getText(this, "facebook"));
    }

    // 保留无参构造函数兼容原有代码(如果需要)
    public PagingToolbar() {
        this(I18nUtils.getInstance()); // 假设原有代码用单例获取I18nUtils
    }
}

然后给A类也注入I18nUtils,这样测试时直接传入Mock实例即可:

class A {
    private final I18nUtils i18n;

    // 构造函数注入I18nUtils
    public A(I18nUtils i18n) {
        this.i18n = i18n;
    }

    void resetToolbar(final ListSelectionModel lastSelectionModel) {
        if (toolbar != null && lastSelectionModel != null) {
            gridPanel.setSelectionModel(lastSelectionModel);
        }
        toolbar = new PagingToolbar(i18n); // 使用带参数的构造函数
    }
}

测试代码就变得非常简洁:

@Test
public void resetToolbar() {
    I18nUtils mockI18n = Mockito.mock(I18nUtils.class);
    when(mockI18n.getText(any(PagingToolbar.class), eq("facebook")))
        .thenReturn("Displaying items...");

    ListSelectionModel listSelectModelMock = Mockito.mock(ListSelectionModel.class);
    A tt = new A(mockI18n);

    tt.resetToolbar(listSelectModelMock);

    // 添加你的验证逻辑
}

这种方式符合面向对象的依赖倒置原则,后续维护和测试都会更轻松。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 13:37:47