JUnit测试中如何通过反射设置PagingToolbar的I18nUtils属性
解决PagingToolbar构造函数依赖I18nUtils的JUnit测试问题
看起来你卡在了测试resetToolbar方法时的空指针问题上——因为PagingToolbar的构造函数直接依赖未初始化的I18nUtils,而且你还必须用new关键字创建这个实例。下面给你几个可行的解决方案:
方案1:用PowerMock拦截new操作(推荐,适合无法修改源码的场景)
PowerMock能拦截类的实例化过程,让我们替换成预先配置好的Mock实例,完美适配你必须new PagingToolbar()的需求。步骤如下:
- 先给测试类添加PowerMock的必要注解:
@RunWith(PowerMockRunner.class) @PrepareForTest(A.class) // 告诉PowerMock要处理A类里的new操作 public class ATest {
- 在测试方法中Mock
I18nUtils,并拦截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
相关产品推荐
相关产品推荐

