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

时间:2015-08-20 21:25:44

标签: java unit-testing reflection mockito powermockito

我有一个名为ConfigReferenceProcessor的类,负责实例化给定类型的新对象并使用提供的配置信息对其进行配置。实例化的对象的类型必须从名为BaseConfigurable的抽象基类派生(在此基类中定义了一些方法,我需要调用这些方法来配置新实例)。 前进的注释: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,因为它在基类中声明为final

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

@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创建对象。

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

1 个答案:

答案 0 :(得分:0)

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

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");
    }
}

然后您可以编写工厂类的单独测试,但那些不需要任何模拟 - 只需传入值并获取和实例,并检查实例的类是否符合您的预期。