2

我有一个名为的类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并尝试使用该方法通过模拟 final 方法来设置我的模拟,并在创建新实例时返回模拟对象:

@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.

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

4

1 回答 1

0

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

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

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

于 2015-08-21T03:31:30.857 回答