使用 PowerMockito 模拟通过反射创建的新实例

Mock new instance created through reflection using PowerMockito

我有一个名为 ConfigReferenceProcessor 的 class,负责实例化给定类型的新对象并使用提供的配置信息配置它们。被实例化的对象的类型必须从一个名为 BaseConfigurable 的抽象基础 class 派生(在这个基础 class 中定义了一些方法,我需要调用这些方法来配置新实例). 后续注意事项:BaseConfigurable 不在我的控制范围内,因此我无法对其代码进行任何更改。

我正在尝试对方法 processConfigReference 进行单元测试,该方法执行以下操作:

public <T extends BaseConfigurable> T processConfigReference(
        Class<T> clazz, ConfigReferenceCfg configRef) {
    // All this in a try/catch
    Class<? extends T> objClass = Class.forName(configRef.getClassName())
                                       .asSubclass(clazz);
    Constructor<? extends T> constructor = objClass.getConstructor();
    T obj = constructor.newInstance();

    // Some methods to setup and configure the new instance, including:
    obj.loadConfig(configRef.getConfigName());

    return obj;
}

在我的单元测试中,我需要控制 loadConfig 方法,因为我不想拖入文件系统查找等整个配置行为。我的第一个想法是简单地创建我自己的模拟对象:

static class MockConfigurable extends BaseConfigurable {

    @Override
    public void loadConfig(String configName) {
        // Do nothing.
    }

    // Mandatory methods
}

不幸的是,我无法覆盖 loadConfig,因为它在基础 class.

中声明为 final

我发现关于使用 PowerMockito 方法创建模拟对象的堆栈溢出的其他问题 whenNew 并尝试通过模拟最终方法并在创建新实例时返回模拟对象来使用它来设置我的模拟:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ TestConfigReferenceProcessor.MockConfigurable.class, ConfigReferenceProcessor.class })
public class TestConfigReferenceProcessor {
    static class MockConfigurable extends BaseConfigurable {
        // Mandatory methods
    }

    @Mock private MockConfigurable mockConfigurable;

    @Before
    public void initializeMockConfigurableInstance() throws Exception {
        doNothing().when(mockConfigurable).loadConfig(any(String.class));
        whenNew(MockConfigurable.class).withAnyArguments().thenReturn(mockConfigurable);
    }

    @Test
    public void shouldProcessConfigRef() {
        MockConfigurable result = 
                ConfigReferenceProcessor.forClass(MockConfigurable.class)
                                        .processConfigReference(configRefCfg);

        // Fails!
        verify(mockConfigurable).loadConfig(any(String.class));
    }
}

但这种方法对我来说似乎不起作用。我怀疑它不起作用,因为我实际上并没有使用 new.

创建对象

还有其他方法可以解决这个问题吗?

BaseConfigurable 是你无法控制的,loadConfig 是最终的,但是你可以用 ConfigReferenceProcessor 做你想做的,对吧?那么,我认为一个好的方法是将事物与另一个间接级别分开。使用 newConfigurable 方法创建 ConfigurableFactory class。然后您可以模拟工厂并验证交互。因此,创建工厂 class:

public class ConfigurableFactory {
    public <T extends BaseConfigurable> T newConfigurable(Class<T> clazz, String className) throws Exception {
        Class<? extends T> objClass = Class.forName(className)
                .asSubclass(clazz);
        Constructor<? extends T> constructor = objClass.getConstructor();
        return constructor.newInstance();
    }

}

然后重构你原来的方法:

public class ConfigReferenceProcessor {

    private ConfigurableFactory configurableFactory = 
        new ConfigurableFactory(); // default instance

    public void setConfigurableFactory(ConfigurableFactory configurableFactory) {
        this.configurableFactory = configurableFactory;
    }

    public <T extends BaseConfigurable> T processConfigReference(
            Class<T> clazz, ConfigReferenceCfg configRef) throws Exception {
        T obj = configurableFactory.newConfigurable(clazz, configRef.getClassName());

        // Some methods to setup and configure the new instance, including:
        obj.loadConfig(configRef.getConfigName());

        return obj;
    }

}

像这样的东西会测试你的方法:

@RunWith(PowerMockRunner.class)
@PrepareForTest(BaseConfigurable.class)
public class ConfigReferenceProcessorTest {

    // public class so the constructor is callable
    public static class MockConfigurable extends BaseConfigurable {
        // Mandatory methods
    }

    @Mock
    private ConfigReferenceCfg configRefCfg;
    @Mock
    private ConfigurableFactory configurableFactory;
    @Mock
    private MockConfigurable mockConfigurable;
    @InjectMocks
    private ConfigReferenceProcessor processorUT;

    @Test
    public void shouldProcessConfigRef() throws Exception {
        final String className = MockConfigurable.class.getName();
        given(configRefCfg.getClassName()).willReturn(className);
        given(configRefCfg.getConfigName()).willReturn("testName");
        given(configurableFactory.newConfigurable(MockConfigurable.class, className)).
            willReturn(mockConfigurable);
        MockConfigurable result =
                processorUT.processConfigReference(MockConfigurable.class, configRefCfg);
        assertThat(result, is(mockConfigurable));
        verify(mockConfigurable).loadConfig("testName");
    }
}

然后您可以编写工厂的单独测试 class,但这些不需要任何模拟 - 只需传入值并获取和实例并检查实例的 class就是你所期望的那样。