Вопрос по powermock, junit, mockito, java – Насмешливый метод getClass с PowerMockito

5

Я хотел бы избежать насмешек над методом getClass () для класса, но, похоже, не могу обойтись без него. Я пытаюсь протестировать класс, который хранит типы классов объектов в HashMap, для конкретного метода, который будет использоваться позже. Краткий пример этого:

public class ClassToTest {
    /** Map that will be populated with objects during constructor */
    private Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();

    ClassToTest() {
        /* Loop through methods in ClassToTest and if they return a boolean and 
           take in an InterfaceA parameter then add them to map */
    }

    public void testMethod(InterfaceA obj) {
        final Method method = map.get(obj.getClass());
        boolean ok;
        if (method != null) {
             ok = (Boolean) method.invoke(this, obj);
        }
        if (ok) {
            obj.run();
        }
    }

    public boolean isSafeClassA(final ClassA obj) {
        // Work out if safe to run and then return true/false
    }

    public boolean isSafeClassB(final ClassB obj) {
       // Work out if safe to run and then return true/fals 
    }

}

public interface InterfaceA {
   void run()
}

public class ClassA implements InterfaceA {
   public void run() {
      // implements method here
   }
}

public class ClassB implements InterfaceA {
    public void run() {
       // implements method here
    }
}

Затем у меня есть тест JUnit, который выглядит примерно так:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class ClassToTestTest {
    private final ClassToTest tester = new ClassToTest();

    @Test
    public void test() {
        MockGateway.MOCK_GET_CLASS_METHOD = true;
        final ClassA classA = spy(new ClassA());
        doReturn(ClassA.class).when(classA).getClass();
        MockGateway.MOCK_GET_CLASS_METHOD = false;

        tester.testMethod(classA);
        verify(classA).run();
    }
}

Моя проблема, хотя внутри метода test () classA.getClass (); будет возвращать ClassA, когда он будет внутри метода testMethod () тестера, он по-прежнему возвращает класс ClassA $ EnhancerByMockitoWithCGLIB $ ... и поэтому мой полезный объект всегда будет нулевым.

Можно ли как-то обойти насмешки над классом или что мне нужно сделать, чтобы это исправить?

Заранее спасибо.

Я согласен, что вы пытаетесь утверждать?testMethod() возвращается пустым, так что нет ничегоassert on, и это не похоже на то, что вы обновляете передаваемый объект параметра. Поэтому я могу только предположить, что есть некоторые вызовы методов изнутри testMethod (), которые вы хотитеverify взаимодействие Brad
Я пытался обновить свой код, чтобы он был немного более точным по сравнению с тем, что я пытаюсь протестировать. Я игнорировал все исключения, которые требуют перехвата / выброса. Sarah Tattersall
Похоже, вам не стоит беспокоиться о насмешках над getClass. Не могли бы вы опубликовать некоторые детали о самом тесте? Vitaliy
Я хочу использовать mockito, чтобы убедиться, что в тесте произошли некоторые действия. Моя карта на самом деле хранит тип класса в методе, так что я могу вызвать метод для каждого из классов, которые реализуют интерфейс, и затем, если соответствующий метод возвращает true (или для класса не было метода), я хочу убедиться, что некоторые Поведение случилось. Sarah Tattersall
Можете ли вы написать свой тест (требования) в псевдокоде и / или предоставить больше кода реализации? Похоже, вы пытаетесь использоватьmap реализовать некоторую форму полиморфного поведения, которая не будет хорошим подходом. Если объект, передаваемый в реализует InterfaceA, то вы должны кодировать к этомуinterface и не пытаться определить его фактический класс реализации (например, ClassA), а затем пытаться выполнить метод, который не определен в интерфейсе. Может быть, я не правильно понял Brad

Ваш Ответ

3   ответа
0

Я тоже сталкивался с подобным вопросом. Я думаю, что вы должны добавить ClassToTest.class в @PrepareForTest, потому что вы хотите смоделировать функцию getClass () в этом классе

5

Ваша проблема на самом деле в том, чтоgetClass являетсяfinal вObjectтак что вы не можете заглушить его с помощью Mockito. Я не могу придумать хороший способ обойти это. Есть одна возможность, которую вы могли бы рассмотреть.

Напишите служебный класс с одним методом

public Class getClass(Object o){
    return o.getClass();
}

и рефакторинг класса, который вы тестируете, чтобы он использовал объект этого служебного класса вместо вызоваgetClass() непосредственно. Затем, сделайте возможным ввод служебного объекта, либо с помощью специального конструктора частного пакета, либо с помощью метода установки.

public class ClassToTest{
    private UtilityWithGetClass utility;
    private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();

    public ClassToTest() {
        this(new UtilityWithGetClass());
    }

    ClassToTest(UtilityWithGetClass utility){
        this.utility = utility;
        // Populate map here
    }

    // more stuff here
}

Теперь, в вашем тесте, сделайте макет объекта и заглушкиgetClass, Вставьте макет в класс, который вы тестируете.

Да, PowerMockito делает. Я все еще пытаюсь понять, что вы пытаетесь проверить, и поэтому не могу понять, насколько этот ответ полезен. Обертывание getClass () не похоже на то, что нужно делать.
Я хочу убедиться, что метод в классе, который я передаю как InterfaceA (т.е. запускаю), вызывается в этом методе с учетом конкретных сценариев. Карта фактически содержит тип класса объектов для некоторых методов безопасности и в зависимости от результата этих методов (они возвращают логическое значение) вызывает метод run в классе. Sarah Tattersall
Я знаю, что существуют ограничения на то, С какими заключительными методами может помочь PowerMockito. Я не знаю,getClass один из тех, которые вы не можете заглушить. Я бы сказал «попробуй и посмотри», но кажется, что ты уже попробовал и видел. Что вы имеете в виду, когда говорите «класс, который я передаю как InterfaceA»? - конечно, объект, который вы передаете, на самом деле не является классом? Либо это?
Я думал, что использование PowerMockito позволило мне заглушить последние методы, хотя? Sarah Tattersall
4

Вау, какая головная боль получить этот тестируемый код. Основные проблемы заключаются в том, что вы не можете использовать фиктивные объекты какkey объекты в ваши звонкиmap.get(obj.getClass())и вы пытаетесьinvoke() потенциально макет объектов для вашего тестирования. Мне пришлось провести рефакторинг тестируемого класса, чтобы мы могли смоделировать функциональность / поведение и проверить его поведение.

Итак, это ваша новая реализация, которая будет протестирована с переменными-членами, отделяющими различные части функцииailail и внедренными тестовым классом

public class ClassToTest {

    MethodStore methodStore;
    MethodInvoker methodInvoker;
    ClassToInvoke classToInvoke;
    ObjectRunner objectRunner;  

    public void testMethod(InterfaceA obj) throws Exception {

        Method method = methodStore.getMethod(obj);

        boolean ok = false;

        if (method != null) {
            ok = methodInvoker.invoke(method, classToInvoke, obj);
        }

        if (ok) {
            objectRunner.run(obj);
        }   
    }

    public void setMethodStore(MethodStore methodStore) {
        this.methodStore = methodStore;
    }

    public void setMethodInvoker(MethodInvoker methodInvoker) {
        this.methodInvoker = methodInvoker;
    }

    public void setObjectRunner(ObjectRunner objectRunner) {
        this.objectRunner = objectRunner;
    }

    public void setClassToInvoke(ClassToInvoke classToInvoke) {
        this.classToInvoke = classToInvoke;
    }
}

Это ваш тестовый класс, который больше не требует PowerMock, потому что онне могу издеваться над классом метода, Он просто возвращает исключение NullPointerException.

public class MyTest {

    @Test
    public void test() throws Exception {

        ClassToTest classToTest = new ClassToTest();

        ClassA inputA = new ClassA();

        // trying to use powermock here just returns a NullPointerException
//      final Method mockMethod = PowerMockito.mock(Method.class);
        Method mockMethod = (new ClassToInvoke()).getClass().getMethod("someMethod");   // a real Method instance

        // regular mockito for mocking behaviour    
        ClassToInvoke mockClassToInvoke = mock(ClassToInvoke.class);
        classToTest.setClassToInvoke(mockClassToInvoke);

        MethodStore mockMethodStore = mock(MethodStore.class);
        classToTest.setMethodStore(mockMethodStore);

        when(mockMethodStore.getMethod(inputA)).thenReturn(mockMethod);

        MethodInvoker mockMethodInvoker = mock(MethodInvoker.class);
        classToTest.setMethodInvoker(mockMethodInvoker);

        when(mockMethodInvoker.invoke(mockMethod,mockClassToInvoke, inputA)).thenReturn(Boolean.TRUE);

        ObjectRunner mockObjectRunner = mock(ObjectRunner.class);
        classToTest.setObjectRunner(mockObjectRunner);

        // execute test method      
        classToTest.testMethod(inputA);

        verify(mockObjectRunner).run(inputA);   
    }
}

Дополнительные классы, которые вам требуются, следующие

public class ClassToInvoke {
    public void someMethod() {};
}

public class ClassA implements InterfaceA {

    @Override
    public void run() {
        // do something
    }
}

public class ClassToInvoke {
    public void someMethod() {};
}

public class MethodInvoker {

    public Boolean invoke(Method method, Object obj, InterfaceA a) throws Exception {
         return (Boolean) method.invoke(obj, a);
    }
}

public class MethodStore {

    Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();

    public Method getMethod(InterfaceA obj) {
        return map.get(obj);
    }
}

Поместите все это в вашу среду IDE, и это пройдет с зеленой полосой ... ух ты!

Похожие вопросы