2015-08-20 2 views
2

У меня есть класс под названием ConfigReferenceProcessor, который отвечает за создание экземпляров новых объектов данного типа и их настройку с использованием предоставленной информации о конфигурации. Тип объекта, который создается, должен основываться на абстрактном базовом классе, который называется BaseConfigurable (В этом базовом классе есть методы, которые мне нужно вызвать для настройки нового экземпляра). Заметка в будущем: BaseConfigurable не в порядке, поэтому я не могу внести никаких изменений в ее код.Мощный новый экземпляр, созданный путем отражения с использованием PowerMockito

Я пытаюсь модульным тестированием методы 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 клюв ause объявлен как 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.

Есть ли другой способ обойти эту проблему?

+0

Вы правы, сомневаетесь, что можете переопределить или издеваться над базовыми материалами отражения Java, такими как 'constructor.newInstance()', но возможно, что-то еще можно сделать раньше. Можете ли вы дать больше информации о том, как «конструктор» входит в картину? Может быть, мы могли бы что-то там сделать. – jhericks

+0

@jhericks Я отредактирую сообщение, чтобы включить более подробную информацию о части отражения, но эффективно, я использую 'Class.forName' для загрузки класса. Точный класс, который мне нужно загрузить, указан в аргументе 'configRef', поэтому класс, который я передаю, просто используется для проверки того, что класс, который я загружаю, является подклассом (у меня есть много разных подсемейств классов, все из которых производятся от' BaseConfigurable'). –

ответ

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

Вы можете написать отдельные тесты фабричный класс, но этим не потребуются никакие макеты - просто передайте значения и получите и экземпляр, и убедитесь, что класс экземпляра - это то, что вы ожидаете от него.

+0

Хорошая идея, но проблема в том, что 'loadConfig' является' final' в 'BaseConfigurable'. Я не могу переопределить его в моем мошенническом объекте. –

+0

Правильно, я пропустил это сначала. Я переписал ответ, чтобы отразить это. (Сейчас совсем другое.) Надеюсь, это сработает для вас. – jhericks

+0

Ах, умный! Чувствовать себя немного странно, чтобы представить эту фабрику и сеттера с единственной целью тестирования, но похоже, что это довольно эффективно решает проблему. Я вложил билет владельцу классов конфигурации, чтобы вытащить методы, которые мне нужны, до интерфейса, но я не знаю, будет ли или когда это изменение будет сделано. Между тем, похоже, я могу двигаться вперед с этим подходом. Благодаря! –

Смежные вопросы