Как мне смоделировать поле @Value с автоматическим связыванием весной с Mockito?
Я использую Spring 3.1.4.RELEASE и Mockito 1.9.5. В моем весеннем классе у меня есть:
@Value("#{myProps['default.url']}")
private String defaultUrl;
@Value("#{myProps['default.password']}")
private String defaultrPassword;
// ...
Из моего теста JUnit, который я сейчас настроил так:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest
{
Я хотел бы смоделировать значение для моего поля "defaultUrl". Обратите внимание, что я не хочу имитировать значения для других полей - я бы хотел оставить их как есть, только поле "defaultUrl". Также обратите внимание, что у меня нет явных методов "сеттера" (например, setDefaultUrl
) в моем классе, и я не хочу создавать какие-либо только для целей тестирования.
Учитывая это, как я могу смоделировать значение для этого одного поля?
9 ответов
Вы можете использовать магию весны ReflectionTestUtils.setField
во избежание внесения каких-либо изменений в ваш код.
Посмотрите этот урок для получения дополнительной информации, хотя она вам, вероятно, не понадобится, поскольку метод очень прост в использовании.
ОБНОВИТЬ
С момента появления Spring 4.2.RC1 теперь можно задавать статическое поле без предоставления экземпляра класса. Смотрите эту часть документации и этот коммит.
Это был уже третий раз, когда я гуглял себя на этот пост, так как я всегда забывал, как насмехаться над полем @Value. Хотя принятый ответ правильный, мне всегда нужно некоторое время, чтобы правильно вызвать вызов setField, так что, по крайней мере, для себя я вставлю пример фрагмента здесь:
Производственный класс:
@Value("#{myProps[‘some.default.url']}")
private String defaultUrl;
Тестовый класс:
import org.springframework.test.util.ReflectionTestUtils;
ReflectionTestUtils.setField(myClassUnderTest, "defaultUrl", "http://foo");
// Note: Don't use MyClassUnderTest.CLASS, use the class itself
// Note: Don't use the referenced string "#{myProps[‘some.default.url']}",
// but simply the FIELDs name ("defaultUrl")
Вы можете использовать эту волшебную аннотацию Spring Test:
@TestPropertySource(properties = { "my.spring.property=20" })
Например, это тестовый класс:
@ContextConfiguration(classes = { MyTestClass.Config.class })
@TestPropertySource(properties = { "my.spring.property=20" })
public class MyTestClass {
public static class Config {
@Bean
MyClass getMyClass() {
return new MyClass ();
}
}
@Resource
private MyClass myClass ;
@Test
public void myTest() {
...
И это класс со свойством:
@Component
public class MyClass {
@Value("${my.spring.property}")
private int mySpringProperty;
...
Я хотел бы предложить соответствующее решение, которое заключается в @Value
поля в качестве параметров для конструктора, вместо использования ReflectionTestUtils
учебный класс.
Вместо этого:
public class Foo {
@Value("${foo}")
private String foo;
}
а также
public class FooTest {
@InjectMocks
private Foo foo;
@Before
public void setUp() {
ReflectionTestUtils.setField(Foo.class, "foo", "foo");
}
@Test
public void testFoo() {
// stuff
}
}
Сделай это:
public class Foo {
private String foo;
public Foo(@Value("${foo}") String foo) {
this.foo = foo;
}
}
а также
public class FooTest {
private Foo foo;
@Before
public void setUp() {
foo = new Foo("foo");
}
@Test
public void testFoo() {
// stuff
}
}
Преимущества этого подхода: 1) мы можем создать экземпляр класса Foo без контейнера зависимостей (это просто конструктор), и 2) мы не связываем наш тест с деталями нашей реализации (отражение связывает нас с именем поля с помощью строки, что может вызвать проблемы, если мы изменим имя поля).
Вы также можете смоделировать конфигурацию вашего свойства в вашем тестовом классе
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest
{
@Configuration
public static class MockConfig{
@Bean
public Properties myProps(){
Properties properties = new Properties();
properties.setProperty("default.url", "myUrl");
properties.setProperty("property.value2", "value2");
return properties;
}
}
@Value("#{myProps['default.url']}")
private String defaultUrl;
@Test
public void testValue(){
Assert.assertEquals("myUrl", defaultUrl);
}
}
Я использовал приведенный ниже код, и он сработал для меня:
@InjectMocks
private ClassABC classABC;
@Before
public void setUp() {
ReflectionTestUtils.setField(classABC, "constantFromConfigFile", 3);
}
Ссылка: https://www.jeejava.com/mock-an-autowired-value-field-in-spring-with-junit-mockito/
Также обратите внимание, что в моем классе нет явных методов "установки" (например, setDefaultUrl), и я не хочу создавать какие-либо только для целей тестирования.
Одним из способов решения этой проблемы является изменение класса для использования Constructor Injection, которое используется для тестирования и Spring инъекций. Нет больше размышлений:)
Таким образом, вы можете передать любую строку используя конструктор:
class MySpringClass {
private final String defaultUrl;
private final String defaultrPassword;
public MySpringClass (
@Value("#{myProps['default.url']}") String defaultUrl,
@Value("#{myProps['default.password']}") String defaultrPassword) {
this.defaultUrl = defaultUrl;
this.defaultrPassword= defaultrPassword;
}
}
И в своем тесте просто используйте его:
MySpringClass MySpringClass = new MySpringClass("anyUrl", "anyPassword");
По возможности я устанавливаю видимость поля как защищенную пакетом, чтобы к нему можно было получить доступ из тестового класса. Я документирую это, используя аннотацию Guava @VisibleForTesting (на случай, если следующий парень задается вопросом, почему это не конфиденциально). Таким образом, мне не нужно полагаться на строковое имя поля, и все остается типобезопасным.
Я знаю, что это противоречит стандартным методам инкапсуляции, которым нас учили в школе. Но как только в команде появилось какое-то согласие пойти по этому пути, я нашел это наиболее прагматичным решением.
Другой способ - использовать аннотациюproperties
поле.
Здесь мы переопределяемexample.firstProperty
свойство:
@SpringBootTest(properties = { "example.firstProperty=annotation" })
public class SpringBootPropertySourceResolverIntegrationTest {
@Autowired private PropertySourceResolver propertySourceResolver;
@Test
public void shouldSpringBootTestAnnotation_overridePropertyValues() {
String firstProperty = propertySourceResolver.getFirstProperty();
String secondProperty = propertySourceResolver.getSecondProperty();
Assert.assertEquals("annotation", firstProperty);
Assert.assertEquals("defaultSecond", secondProperty);
}
}
Как видите, он переопределяет только одно свойство. Свойства, не упомянутые в «Оставайтесь нетронутыми». Поэтому это отличное решение, когда нам нужно переопределить только определенные свойства для теста.
Для одного свойства вы можете написать его без фигурных скобок:
@SpringBootTest(properties = "example.firstProperty=annotation")
Ответ от: https://www.baeldung.com/spring-tests-override-properties#springBootTest
Я также рекомендую вам, когда это возможно, передавать свойство в качестве параметра в конструкторе, как в ответе Дерика ( /questions/8671674/kak-mne-smodelirovat-pole-value-s-avtomaticheskim-svyazyivaniem-vesnoj-s-mockito/8671707#8671707), поскольку это позволяет вам легко имитировать свойства в модульных тестах.
Однако в интеграционных тестах вы часто не создаете объекты вручную, а:
- ты используешь
@Autowired
- вы хотите изменить свойство, используемое в классе, который косвенно используется в вашем интеграционном тесте, поскольку это глубокая зависимость от некоторого непосредственно используемого класса.
то это решение с@SpringBootTest
может быть полезно.