Spring инъекция значения в mockito
Я пытаюсь написать тестовый класс для следующего метода
public class CustomServiceImpl implements CustomService {
@Value("#{myProp['custom.url']}")
private String url;
@Autowire
private DataService dataService;
Я использую введенное значение url в одном из методов в классе.
Чтобы проверить это, я написал класс junit
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public CustomServiceTest{
private CustomService customService;
@Mock
private DataService dataService;
@Before
public void setup() {
customService = new CustomServiceImpl();
Setter.set(customService, "dataService", dataService);
}
...
}
public class Setter {
public static void set(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
В applicationContext-test.xml я загружаю файл свойств, используя
<util:properties id="myProp" location="myProp.properties"/>
Но значение url не загружается в CustomService при запуске теста.
Мне было интересно, если все-таки это сделать.
Спасибо
Ответы
Ответ 1
Вы можете autowire в мутаторе (сеттер), а не просто аннотировать частное поле. Затем вы можете использовать этот сеттер из своего тестового класса. Нет необходимости делать это общедоступным, пакет private будет делать, поскольку Spring может все же получить к нему доступ, но в противном случае может быть получен только ваш тест (или другой код в том же пакете).
@Value("#{myProp['custom.url']}")
String setUrl( final String url ) {
this.url = url;
}
Я не поклонник autowiring по-разному (по сравнению с моей кодовой базой) только для тестирования, но альтернатива изменения тестируемого класса из теста просто нечестива.
Ответ 2
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public CustomServiceTest{
@InjectMock
private CustomServiceImpl customService;
@Mock
private DataService dataService;
@Before
public void setup() {
ReflectionTestUtils.setField(customService, "url", "http://someurl");
}
...
}
Ответ 3
Я согласен с комментарием @skaffman.
Кроме того, в вашем тесте используется MockitoJUnitRunner
, поэтому он не будет искать какой-либо материал Spring, это единственная цель - инициализировать Mockito mocks. ContextConfiguration
недостаточно для проводки событий с помощью spring. Технически с JUnit вы можете использовать следующий бегун, если хотите Spring связанные вещи: SpringJUnit4ClassRunner
.
Также, когда вы пишете Unit Test, вам может потребоваться пересмотреть использование spring. Использование проводки Spring в unit test неверно. Однако, если вы вместо этого пишете Тест интеграции, то почему вы используете Mockito там, это не имеет смысла (как сказал скаффман)!
РЕДАКТИРОВАТЬ: Теперь в вашем коде вы прямо устанавливаете CustomerServiceImpl
в своем блоке до этого, это тоже не имеет смысла. Spring вообще не участвует!
@Before
public void setup() {
customService = new CustomServiceImpl();
Setter.set(customService, "dataService", dataService);
}
РЕДАКТИРОВАТЬ 2: Если вы хотите написать Unit Test of CustomerServiceImpl
, то не используйте материал Spring и сразу вводите значение свойства. Также вы можете использовать Mockito для вставки DataService
mock straigth в тестируемый экземпляр.
@RunWith(MockitoJUnitRunner.class)
public CustomServiceImplTest{
@InjectMocks private CustomServiceImpl customService;
@Mock private DataService dataService;
@Before void inject_url() { customerServiceImpl.url = "http://..."; }
@Test public void customerService_should_delegate_to_dataService() { ... }
}
Как вы могли заметить, я использую прямой доступ к полю url
, поле может быть видимым пакетом. Это тестовое обходное решение, чтобы на самом деле ввести значение URL, поскольку Mockito вводит только mocks.
Ответ 4
Вы не должны издеваться над тем, что вы пытаетесь проверить. Это бессмысленно, поскольку вы не будете касаться какого-либо кода, который вы пытаетесь проверить. Вместо этого получите экземпляр CustomerServiceImpl
из контекста.
Ответ 5
У меня был список строк из файла свойств. Метод ReflectionTestUtils class SetField, используемый в блоке @Before, помог мне установить эти значения до моего выполнения теста. Он отлично работал даже для моего слоя dao, который зависит от класса Common DaoSupport.
@Before
public void setList() {
List<String> mockedList = new ArrayList<>();
mockedSimList.add("CMS");
mockedSimList.add("SDP");
ReflectionTestUtils.setField(mockedController, "ActualListInController",
mockedList);
}
Ответ 6
Вы можете использовать этот небольшой класс утилиты (gist) для автоматического ввода значений полей в целевой класс:
public class ValueInjectionUtils {
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER =
new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX,
SystemPropertyUtils.VALUE_SEPARATOR, true);
public static void injectFieldValues(Object testClassInstance, Properties properties) {
for (Field field : FieldUtils.getFieldsListWithAnnotation(testClassInstance.getClass(), Value.class)) {
String value = field.getAnnotation(Value.class).value();
if (value != null) {
try {
Object resolvedValue = resolveValue(value, properties);
FieldUtils.writeField(field, testClassInstance, CONVERSION_SERVICE.convert(resolvedValue, field.getType()),
true);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
}
private static Object resolveValue(String value, Properties properties) {
String replacedPlaceholderString = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(value, properties);
return evaluateSpEL(replacedPlaceholderString, properties);
}
private static Object evaluateSpEL(String value, Properties properties) {
Expression expression = EXPRESSION_PARSER.parseExpression(value, new TemplateParserContext());
EvaluationContext context =
SimpleEvaluationContext.forPropertyAccessors(new MapAccessor()).withRootObject(properties).build();
return expression.getValue(context);
}
}
Он использует org.apache.commons.lang3.reflect.FieldUtils
для доступа ко всем полям, аннотированным с помощью @Value
а затем использует Spring-классы для решения всех значений placeholder. Вы также можете изменить тип properties
параметра на PlaceholderResolver
если хотите использовать свой собственный PlaceholderResolver. В своем тесте вы можете использовать его для ввода набора значений, заданных как экземпляр Map
или Properties
например, в следующем примере:
HashMap<String, Object> props = new HashMap<>();
props.put("custom.url", "http://some.url");
Properties properties = new Properties();
properties.put("myProp", props);
ValueInjectionUtils.injectFieldValues(testTarget, properties);
Это будет пытаться решить все @Value
аннотированных полей в вашем dataService
. Я лично предпочитаю это решение через ReflectionTestUtils.setField(dataService, "field", "value");
так как вам не нужно полагаться на имена жестко закодированных полей.