Как смоделировать конструкцию объекта, созданную с помощью отражения, т.е. метода newInstance () - PullRequest
0 голосов
/ 05 мая 2019

У меня есть фрагмент кода ниже, где класс Employee создает объект AppraisalCalculator, используя отражение. Я хочу смоделировать этот AppraisalCalculator объект с помощью PowerMockito.

class AppraisalCalculator {

    public int appraisal() {
        return 300;
    }
}

class Employee {

    public int updateSalary() {

        // line 1
        AppraisalCalculator ac = 
            AppraisalCalculator.class.getConstructor().newInstance();

        return ac.appraisal();
    }
}

class TestRunner {

    @Test
    public void test() {

        AppraisalCalulator acMock=PowerMockito.mock(AppraisalCalculator.class);   
        PowerMockito
            .whenNew(AppraisalCalculator.class)
            .withNoArguments()
            .thenReturn(600);

        Employee emp = new Employee();

        int actualValue = emp.updateSalary();
        int expectedValue=600;
        Assert.equals(expectedValue,actualValue);
    }
}

Здесь, хотя я и издевался над объектом калькулятора оценки, он все равно вызывает реальный метод appraisal() из AppraisalCalculator. Если фактическое значение AppraisalCalculator в строке 1 создается с использованием нового оператора вместо newInstance(), тогда это смоделирование работает.

Может кто-нибудь объяснить, почему это не работает, если реальный объект создан с использованием отражения? Что я могу сделать, чтобы издеваться над этим объектом в таком сценарии?

1 Ответ

0 голосов
/ 07 мая 2019

Позвольте мне сначала начать с перефразирования вашего вопроса полностью рабочим кодом.

@RunWith(PowerMockRunner.class)
@PrepareForTest(Employee.class)
public class TestRunner {

    @Test
    public void test() throws Exception {

        AppraisalCalculator acMock = PowerMockito.mock(AppraisalCalculator.class);
        PowerMockito
                .whenNew(AppraisalCalculator.class)
                .withNoArguments()
                .thenReturn(acMock);

        when(acMock.appraisal()).thenReturn(600);

        Employee emp = new Employee();

        int actualValue = emp.updateSalary();
        int expectedValue = 600;
        assertEquals(expectedValue, actualValue);
    }
}

Затем PowerMock работает так, что PowerMockRunner будет смотреть на каждый класс, который необходимо подготовить (здесь Employee), затем найдите вызов конструктора, который мы хотим заменить, и сделайте это.Это делается при загрузке класса.Байт-код реального класса, вызывающий конструктор, заменяется на тот, который возвращает макет.

Дело в том, что если вы используете отражение, PowerMock не может узнать, прочитав байт-код, что этот конструктор будет вызван.Это будет известно только динамически после этого.Так что никакой насмешки не делается.

Если вам действительно нужно создать класс, который будет смоделирован отражением, я бы на самом деле немного изменил код.

В Employee Я бы добавил что-то вроде

protected AppraisalCalculator getCalculator() {
    try {
        return AppraisalCalculator.class.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

while - это метод, предназначенный для выделения конструкции калькулятора.

, просто создайте дочерний класс

    @Test
    public void testWithChildClass() {

        // Note that you don't need PowerMock anymore
        AppraisalCalculator acMock = Mockito.mock(AppraisalCalculator.class);
        when(acMock.appraisal()).thenReturn(600);

        Employee emp = new Employee() {
            @Override
            protected AppraisalCalculator getCalculator() {
                return acMock;
            }
        };

        int actualValue = emp.updateSalary();
        int expectedValue = 600;
        assertEquals(expectedValue, actualValue);
    }

или частичный макет (шпион)

    @Test
    public void test2() {

        // No PowerMock either here
        AppraisalCalculator acMock = Mockito.mock(AppraisalCalculator.class);
        when(acMock.appraisal()).thenReturn(600);

        Employee emp = spy(new Employee());
        doReturn(acMock).when(emp).getCalculator();

        int actualValue = emp.updateSalary();
        int expectedValue = 600;
        assertEquals(expectedValue, actualValue);
    }
...