Как мне смоделировать поле @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" }) 

смотрите https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/TestPropertySource.html

Например, это тестовый класс:

@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может быть полезно.

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