Весенний впрыск значения в мокито - PullRequest
19 голосов
/ 09 февраля 2012

Я пытаюсь написать тестовый класс для следующего метода

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 при запуске теста. Мне было интересно, если есть в любом случае, чтобы сделать это.

Спасибо

Ответы [ 6 ]

44 голосов
/ 02 марта 2015
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");
}    
...
}
12 голосов
/ 09 февраля 2012

Я согласен с комментарием @ skaffman.

Кроме того, в вашем тесте используется MockitoJUnitRunner, поэтому он не будет искать какой-либо материал Spring, это единственная цель - инициализировать mockito mocks.ContextConfiguration недостаточно, чтобы связать вещи с пружиной.Технически с JUnit вы можете использовать следующий бегун, если вы хотите что-то связанное с пружиной: SpringJUnit4ClassRunner.

Также, когда вы пишете Unit Test , вы можете пересмотреть использование пружины.Использование пружинной проводки в модульном тесте не правильно.Однако если вы вместо этого пишете Integration Test , то почему вы используете Mockito там, это не имеет смысла (как сказал skaffman)!

EDIT: Теперь в вашем коде вы прямо устанавливаете CustomerServiceImpl в блоке before, что также не имеет смысла.Spring вообще не задействован!

@Before
public void setup() {
    customService = new CustomServiceImpl();
    Setter.set(customService, "dataService", dataService);
}

EDIT 2: Если вы хотите написать Unit Test из CustomerServiceImpl, тогда избегайте Spring ивпрыскивать непосредственно стоимость имущества.Кроме того, вы можете использовать Mockito для вставки макета DataService в тестируемый экземпляр.

@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 вводит только макеты.

3 голосов
/ 20 сентября 2013

Вы можете автоматически подключаться к мутатору (сеттеру), а не просто аннотировать приватное поле.Затем вы можете использовать этот сеттер из вашего тестового класса.Не нужно делать его общедоступным, пакет private будет работать, поскольку Spring по-прежнему может получить к нему доступ, но в противном случае туда может попасть только ваш тест (или другой код в том же пакете).

@Value("#{myProp['custom.url']}")
String setUrl( final String url ) {
    this.url  = url;
}

Я не являюсьфанат автопроводки по-другому (по сравнению с моей базой кода) только для тестирования, но альтернатива изменению тестируемого класса из теста просто нечестивая.

1 голос
/ 03 февраля 2016

У меня был список чтения строк из файла свойств. Метод setField класса ReflectionTestUtils, используемый в блоке @Before, помог мне установить эти значения перед выполнением теста. Он отлично работал даже для моего слоя дао, который зависит от класса Common DaoSupport.

@Before
public void setList() {
    List<String> mockedList = new ArrayList<>();
    mockedSimList.add("CMS");
    mockedSimList.add("SDP");
    ReflectionTestUtils.setField(mockedController, "ActualListInController",
            mockedList);
}
1 голос
/ 09 февраля 2012

Не стоит издеваться над тем, что вы пытаетесь проверить. Это бессмысленно, поскольку вы не будете касаться кода, который пытаетесь протестировать. Вместо этого получите экземпляр CustomerServiceImpl из контекста.

0 голосов
/ 04 декабря 2018

Вы можете использовать этот небольшой служебный класс ( 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 для разрешения всех значений заполнителей.Вы также можете изменить тип параметра 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");, так как вам не нужно полагаться на жестко закодированные имена полей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...