Як я знущаюсь над Mockito з автоматичного проводового поля @Value навесні?


124

Я використовую 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в моєму класі явних методів «встановлення» (наприклад ) немає, і я не хочу створювати будь-які лише для цілей тестування.

З огляду на це, як я можу висміяти значення для цього одного поля?

Відповіді:


145

Ви можете використовувати магію Spring, ReflectionTestUtils.setFieldщоб уникнути будь-яких змін у свій код.

Перегляньте цей підручник для отримання додаткової інформації, хоча він вам, мабуть, не знадобиться, оскільки метод дуже простий у використанні

ОНОВЛЕННЯ

З моменту введення Spring 4.2.RC1 тепер можна встановити статичне поле без необхідності подавати екземпляр класу. Дивіться цю частину документації та цей документ .


12
На випадок, якщо посилання загинула: використовуйте ReflectionTestUtils.setField(bean, "fieldName", "value");перед тим, як викликати свій beanметод під час тесту.
Michał Stochmal

2
Гарне рішення для глузування з властивостей, які витягуються з файлу властивостей.
Антоні Сампат Кумар Редді

@ MichałStochmal, виконуючи це з моменту подання, це приватний java.lang.IllegalStateException: Не вдалося отримати доступ до методу: Клас org.springframework.util.ReflectionUtils не може отримати доступ до члена класу com.kaleidofin.app.service.impl.CVLKRAProvider з модифікаторами "" на org.springframework.util.ReflectionUtils.handleReflectionException (ReflectionUtils.java:112) на org.springframework.util.ReflectionUtils.setField (ReflectionUtils.java:655)
Ахіл Сурапурам

113

Зараз я вже втретє заглянув у цю посаду SO, оскільки я завжди забуваю, як знущатися над полем @Value. Хоча прийнята відповідь правильна, мені завжди потрібен певний час, щоб отримати правильний виклик "setField", тому принаймні для себе я вставлю сюди фрагмент прикладу:

Виробничий клас:

@Value("#{myProps[‘some.default.url']}")
private String defaultUrl;

Тестовий клас:

import org.springframework.test.util.ReflectionTestUtils;

ReflectionTestUtils.setField(instanceUnderTest, "defaultUrl", "http://foo");
// Note: Don't use MyClassUnderTest.class, use the instance you are testing itself
// Note: Don't use the referenced string "#{myProps[‘some.default.url']}", 
//       but simply the FIELDs name ("defaultUrl")

32

Ви також можете змайструвати конфігурацію власності у своєму тестовому класі

@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);
   }
}

25

Я хотів би запропонувати відповідне рішення, яке полягає в тому, щоб передати @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) ми не зв'язуємо наш тест з деталями нашої реалізації (відображення пов'язує нас із назвою поля за допомогою рядка, що може спричинити проблему, якщо ми змінимо ім'я поля).


3
зворотний бік: Якщо хтось заплутався з анотацією, наприклад, використовує властивість 'bar' замість «foo», ваш тест все одно буде працювати. Я просто маю цей випадок.
Нілс Ель-Хімод

@ NilsEl-Himoud Це взагалі справедливий момент щодо питання про ОП, але питання, яке ви ставите, не є кращим чи гіршим за допомогою утилітів відображення проти конструктора. Суть цієї відповіді полягала в розгляді конструктора над утилітою відображення (прийнята відповідь). Марк, спасибі за відповідь, я ціную легкість та чистоту цього підключення.
Marquee

22

Ви можете використовувати цю магічну анотацію весняного тесту:

@TestPropertySource(properties = { "my.spring.property=20" }) 

див. org.springframework.test.context.TestPropertySource

Наприклад, це тестовий клас:

@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;
   ...

13

Я використовував наведений нижче код, і він працював на мене:

@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/


1
те саме тут ... + 1
Ніжний

Я добрий зробив те саме, але його все ще не відображає
Shubhro Mukherjee

1

Також зауважте, що я не маю явних методів "setter" (наприклад, setDefaultUrl) у своєму класі, і я не хочу створювати будь-які лише для цілей тестування.

Один із способів вирішити це - змінити свій клас на використання Constructor Injection , який використовується для тестування та Spring Spring. Немає більше роздумів :)

Отже, ви можете передавати будь-яку String за допомогою конструктора:

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");
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.