Смоделируйте финальный класс и внедрите его в элемент данных с автоматическим связыванием и преодолите вызов метода postConstruct

Я хочу провести модульное тестирование Java-класса с конечным классом объекта с автопроводкой, а также другим классом с автопроводкой, у которого есть метод @PostConstruct. Хотя их можно проверить по отдельности, я не могу их объединить.

Этот вопрос является дополнением к вопросу о введении mockito mocks в бобовые

Код для тестирования

public class A { 
    @Autowired
    private FinalClass serviceClient;
    @Autowired
    private ClassWithPostConstructor resourceVerifier;

    //no setters or constructors

    public String useBothFinalClassAndClassWithPostConstructor() {
        //logic to be tested
    }
}

Рабочий тестовый класс

@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class ATest {
    //@org.mockito.Mock //fails to mock final class
    @org.powermock.api.easymock.annotation.Mock
    private FinalClass serviceClient;
    @org.powermock.api.easymock.annotation.Mock
    private ClassWithPostConstructor resourceVerifier;
    //other mock objects required for mocking the services

    //@InjectMocks //fails since mocking final class
    private A a;

    @Before
    public void init() {
        a = new A();
        //working snippet with setters created in A and without @Autowired here within the test
        serviceClient = PowerMock.create(FinalClass.class);
        a.setServiceClient(serviceClient);
        resourceVerifier = PowerMock.create(ClassWithPostConstructor.class);
        a.setClassWithPostConstructor(resourceVerifier);
    }

    @Test
    public void testTheMethodUsingExpectAndVerify() {
        //test the functionality here
        EasyMock.expect(serviceClient.callService()).andReturn("someMock");
        EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
        PowerMock.replayAll();
        A.useBothFinalClassAndClassWithPostConstructor();
        PowerMock.verifyAll();
    }
}

Приведенный выше код работает с необходимостью установки в файле

Ожидаемый тестовый класс

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:spring-configuration/unit-testing-config.xml"})
@PrepareForTest(FinalClass.class)
public class ATest {
    @Autowired
    private FinalClass serviceClient;
    @Autowired
    private ClassWithPostConstructor resourceVerifier;
    //other mock objects required for mocking the services

    private A a;

    @Before
    public void init() {
        a = new A();
    }

    @Test
    public void testTheMethodUsingExpectAndVerify() {
        //test the functions here
        EasyMock.expect(serviceClient.callService()).andReturn("someMock");
        EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
        PowerMock.replayAll();
        A.useBothFinalClassAndClassWithPostConstructor();
        PowerMock.verifyAll();
    }
}

//spring-configuration/unit-testing-config.xml
//same error even on customer factory
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
    <constructor-arg type="java.lang.Class" value="com.company...resourceVerifier" /> 
</bean>
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
    <constructor-arg type="java.lang.Class" value="com.company...serviceClient" /> 
</bean>

Приведенный выше фрагмент фальсифицирует finalClass, но вызывает @PostConstructor из ResourceVerifier.class - что нужно сделать здесь, чтобы преодолеть этот вызов.


исследования

  • Можно протестировать файлы с автопроводкой, используя @InjectMocks, без необходимости настройки контекста Spring.
  • @InjectMock молча завершается сбоем для статических и конечных полей, а при сбое не внедряет и другие макеты.
  • Можно смоделировать последний класс с помощью метода PowerMock createMock и запустить тест с PowerMockRunner и @PrepareForTest. Но для этого нужны новые ненужные сеттеры, чтобы вводить макеты для полей @Autowired.
  • MockitoAnnotations. @ Mock не очень хорошо работает с PowerMock (особенно когда имитирует объекты конечного класса) и может быть решена с помощью EasyMock.Annotations. @ Mock
  • EasyMock и PowerMock не имеют аннотации @InjectMocks для инъекций макетов, насколько это возможно с помощью Mockito (это решило бы проблему в секундах).
  • Можно внедрять пружинные бины с автопроводкой через SpringJUnit4Runner и отдельное модульное тестирование @ContextConfiguration
  • Можно запустить один и тот же тестовый файл с PowerMockRunner и SpringJUnit4Runner с PowerMockRunnerDelegate
  • Я знаю, что метод @PostConstruct не будет выполняться автоматически, если в макете используется код, а не с использованием создания и внедрения Spring Bean.
  • Если класс фабричного компонента-оболочки написан и используется для создания макетов, он внедряется автоматически, но вместе с ним вызывает и метод @PostConstruct.
  • Невозможно зависеть от Springockito, поскольку на этом этапе он ненадежен.

Но ни один из них не работал, так как сценарий использования представляет собой комбинацию всех этих.


Возможные решения

  • Удалите поля @Autowired и используйте инъекции Setter так, чтобы это было возможно путем обычного насмешки с использованием PowerMock (проверено на работоспособность) - но это соглашение, за которым следуют пакеты внешних команд - я должен стараться изо всех сил придерживаться его.
  • Или установите @Autowired для сеттеров или для конструктора

Альтернативы?

Я не чувствую, что классы требуют реорганизации, поскольку они служат своим целям и также хорошо спроектированы.

  • Любые другие средства, которые не требуют держать под контролем тестируемый класс. Что если у меня не было разрешений на изменение этого класса? т. е. чисто зависимое от библиотеки решение для тестирования.
  • Не уверен, возможно ли это с помощью PowerMockito? Не пробовал комбинацию PowerMockito с PowerMock.

1 ответ

Решение

Хм,

Не уверен, возможно ли это с помощью PowerMockito? Не пробовал комбинацию PowerMockito с PowerMock.

Мне кажется, у вас в голове беспорядок и неправильное понимание PowerMock / Mockito и EasyMock.

Вы никогда не должны использовать в то же время PowerMockito а также PowerMockпотому что эти два класса являются дружественным к PowerMock API для двух разных фреймворков: EasyMock и Mockito. И нет никаких оснований использовать их обоих.

И конечно это хочу работать

//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services

//@InjectMocks //fails since mocking final class
private A a;

Потому что вы замедляете и создаете макеты через EasyMock API, но пытаетесь внедрить его с помощью аннотации Mockito.

Вы должны выбрать только один Mocking Framework и использовать для него подходящий API. На данный момент Mockito + PowerMockito (PowerMock API для Mockito) лучше соответствуют вашим требованиям.

Вы можете привести полный пример того, как это работает на PowerMock GitHub

@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class SpringInjectFinalClassExampleTest {

    @Mock
    private FinalClass finalClass;

    @InjectMocks
    private MyBean myBean = new MyBean();;

    @Test
    public void testInjectFinalClass() {
        final String value = "What's up?";
        when(finalClass.sayHello()).thenReturn(value);

        assertEquals(value, myBean.sayHello());

    }

}
Другие вопросы по тегам