代码之家  ›  专栏  ›  技术社区  ›  Jason Bullers

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

  •  2
  • Jason Bullers  · 技术社区  · 10 年前

    我有一个班叫 ConfigReferenceProcessor 其负责实例化给定类型的新对象并使用所提供的配置信息配置它们。实例化的对象的类型必须派生自名为 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
    }
    

    不幸地 我无法超越 加载配置 因为它被声明为 final 在基类中 .

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

    @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 回复  |  直到 10 年前
        1
  •  0
  •   jhericks    10 年前

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

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