Внедрение свойства String с помощью @InjectMocks

У меня есть Spring MVC @Controller с этим конструктором:

@Autowired
public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

Я хочу написать автономный unit test для этого контроллера:

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    private String myProperty = "my property value";

    @InjectMocks
    private AbcController controllerUnderTest;

    /* tests */
}

Есть ли способ получить @InjectMocks для ввода моего свойства String? Я знаю, что не могу издеваться над String, поскольку он неизменен, но могу ли я просто ввести нормальную строку здесь?

@InjectMocks вводит значение null по умолчанию в этом случае. @Mock понятно генерирует исключение, если я положил его на myProperty. Есть ли еще одна аннотация, которую я пропустил, просто означает "ввести этот точный объект, а не макет"?

Ответ 1

Вы не можете сделать это с Mockito, потому что, как вы упомянули сами, String является final и не может быть высмеяна.

Существует аннотация @Spy которая работает с реальными объектами, но имеет те же ограничения, что и @Mock, поэтому вы не можете шпионить за String.

Там нет аннотации, чтобы сказать Mockito просто ввести это значение без каких-либо насмешек или шпионажа. Хотя это было бы неплохо. Возможно, предложите это в хранилище Mockito Github.

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

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

@Component
public class MyProperty {

    @Value("${my.property}")
    private String myProperty;

    ...
}

Это может быть введено в контроллер.

@Autowired
public AbcController(XyzService xyzService, MyProperty myProperty) { 
    ... 
}

Вы можете посмеяться над этим и впрыснуть.

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @Mock
    private MyProperty myProperty;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp(){
        when(myProperty.get()).thenReturn("my property value");
    }

    /* tests */
}

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

Ответ 2

Вы не можете сделать это с помощью Mockito, но у Apache Commons есть способ сделать это, используя одну из встроенных утилит. Вы можете поместить это в функцию в JUnit, которая запускается после того, как Mockito вводит остальную часть mocks, но прежде чем ваши тестовые сценарии будут запущены, например:

@InjectMocks
MyClass myClass;

@Before
public void before() throws Exception {
    FieldUtils.writeField(myClass, "fieldName", fieldValue, true);
}

Ответ 3

Поскольку вы используете Spring, вы можете использовать org.springframework.test.util.ReflectionTestUtils из модуля spring-test. Он аккуратно оборачивает установку поля в объекте или статическое поле в классе (вместе с другими служебными методами).

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp(){
        ReflectionTestUtils.setField(controllerUnderTest, "myProperty", 
               "String you want to inject");
    }

    /* tests */
}

Ответ 4

Только не используйте @InjectMocks в этом случае.

делать:

@Before
public void setup() {
 controllerUnderTest = new AbcController(mockXyzService, "my property value");
}

Ответ 5

То, что вы можете использовать, это: org.mockito.internal.util.reflection.Whitebox

  1. Рефакторинг вашего конструктора класса "AbcController"
  2. В своем методе класса Test "before" используйте метод Whitebox.setInternalState, чтобы указать любую желаемую строку.

    @Before
    public void setUp(){
        Whitebox.setInternalState(controllerUnderTest, "myProperty", "The string that you want"); }
    

Ответ 6

Решение простое: Вы должны поместить инжектор конструктора для типа объекта, в то время как для примитивных/конечных зависимостей вы можете просто использовать инжектор сеттера, и это подойдет для этого сценария.

Итак, это:

public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

будет изменено на:

@Autowired
public AbcController(XyzService xyzService) {/*...*/}

@Autowired
public setMyProperty(@Value("${my.property}") String myProperty){/*...*/}

И инъекции @Mock в тесте будут такими же простыми, как:

@Mock
private XyzService xyzService;

@InjectMocks
private AbcController abcController;

@BeforeMethod
public void setup(){
    MockitoAnnotations.initMocks(this);
    abcController.setMyProperty("new property");
}

И этого будет достаточно. Переход на Reflections не рекомендуется!

ПОЖАЛУЙСТА, ИЗБЕГАЙТЕ ИСПОЛЬЗОВАНИЯ ОТРАЖЕНИЙ В ПРОИЗВОДСТВЕННОМ КОДЕКСЕ, КАК МОЖНО ВОЗМОЖНО !!

Для решения Jan Groot я должен напомнить вам, что это станет очень неприятным, так как вам придется удалить все @Mock и даже @InjectMocks и придется инициализировать, а затем ввести их вручную, что для двух зависимостей звучит легко, но для 7 зависимостей код становится кошмаром (см. ниже).

private XyzService xyzService;
private AbcController abcController;

@BeforeMethod
public void setup(){ // NIGHTMARE WHEN MORE DEPENDENCIES ARE MOCKED!
    xyzService = Mockito.mock(XyzService.class);
    abcController = new AbcController(xyzService, "new property");
}