Заполнение Spring @Value во время Unit Test

Я пытаюсь написать Unit Test для простого bean, который использовался в моей программе для проверки форм. bean аннотируется с помощью @Component и имеет переменную класса, которая инициализируется с помощью @Value("${this.property.value}") private String thisProperty;

Я хотел бы написать модульные тесты для методов проверки внутри этого класса, однако, если возможно, я хотел бы сделать это без использования файла свойств. Мое рассуждение в этом заключается в том, что если значение, которое я вытягиваю из файла свойств, меняет, я бы хотел, чтобы это не повлияло на мой тестовый пример. Мой тестовый пример проверяет код, который проверяет значение, а не само значение.

Есть ли способ использовать Java-код внутри моего тестового класса для инициализации класса Java и заполнения свойства Spring @Value внутри этого класса, а затем использовать его для тестирования с помощью <

Я нашел этот How To, который кажется закрытым, но все еще использует файл свойств. Я бы предпочел, чтобы это был Java-код.

Спасибо

Ответ 1

Если возможно, я попытаюсь написать те тесты без Spring Context. Если вы создадите этот класс в своем тесте без spring, тогда вы получите полный контроль над его полями.

Чтобы установить поле @value, вы можете использовать Springs ReflectionTestUtils - у него есть метод setField, чтобы установить частные поля.

@see JavaDoc: ReflectionTestUtils.setField(java.lang.Object, java.lang.String, java.lang.Object)

Ответ 2

Поскольку Spring 4.1 вы можете настроить значения свойств только в коде, используя аннотацию org.springframework.test.context.TestPropertySource на уровне класса Unit Tests. Вы можете использовать этот подход даже для ввода свойств в зависимые bean экземпляры

Например

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FooTest.Config.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }


  @Configuration
  static class Config {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertiesResolver() {
        return new PropertySourcesPlaceholderConfigurer();
    }

  }

}

Примечание. Необходимо иметь экземпляр org.springframework.context.support.PropertySourcesPlaceholderConfigurer в Spring контексте

Редактировать 24-08-2017: Если вы используете SpringBoot 1.4.0 и позже, вы можете инициализировать тесты с помощью @SpringBootTest и @SpringBootConfiguration аннотации. Подробнее здесь

В случае SpringBoot мы имеем следующий код

@SpringBootTest
@SpringBootConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }

}

Ответ 3

Если вы хотите, вы все равно можете выполнить свои тесты в Spring Контекст и установить необходимые свойства внутри класса конфигурации Spring. Если вы используете JUnit, используйте SpringJUnit4ClassRunner и определите выделенный класс конфигурации для своих тестов следующим образом:

Испытуемый класс:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

    @Value("${someProperty}")
    private String someProperty;
}

Класс тестирования:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = SomeClassTestsConfig.class)
public class SomeClassTests {

    @Autowired
    private SomeClass someClass;

    @Autowired
    private SomeDependency someDependency;

    @Before
    public void setup() {
       Mockito.reset(someDependency);

    @Test
    public void someTest() { ... }
}

И класс конфигурации для этого теста:

@Configuration
public class SomeClassTestsConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() throws Exception {
        final PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();

        properties.setProperty("someProperty", "testValue");

        pspc.setProperties(properties);
        return pspc;
    }
    @Bean
    public SomeClass getSomeClass() {
        return new SomeClass();
    }

    @Bean
    public SomeDependency getSomeDependency() {
        // Mockito used here for mocking dependency
        return Mockito.mock(SomeDependency.class);
    }
}

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

Ответ 4

Не злоупотребляйте приватными полями, чтобы получить/установить с помощью отражения

Использование рефлексии, как это делается в нескольких ответах, - это то, чего мы могли бы избежать.
Это приносит небольшую ценность здесь, в то время как это имеет много недостатков:

  • мы обнаруживаем проблемы с отражением только во время выполнения (например, поля больше не существуют)
  • Мы хотим инкапсуляцию, но не непрозрачный класс, который скрывает зависимости, которые должны быть видны, и делает класс более непрозрачным и менее тестируемым.
  • это поощряет плохой дизайн. Сегодня вы объявляете @Value String field. Завтра вы можете объявить 5 или 10 о них в этом классе, и вы можете даже не знать, что вы уменьшаете дизайн класса. При более наглядном подходе к установке этих полей (например, в конструкторе) вы дважды подумаете, прежде чем добавлять все эти поля, и, вероятно, вы инкапсулируете их в другой класс и используете @ConfigurationProperties.

Сделайте свой класс тестируемым как в унитарном, так и в интеграционном режиме

Чтобы иметь возможность писать как простые модульные тесты (то есть без работающего контейнера Spring), так и интеграционные тесты для вашего класса компонентов Spring, вы должны сделать этот класс пригодным для использования с или без Spring.
Запуск контейнера в модульном тесте, когда он не требуется, является плохой практикой, которая замедляет локальные сборки: вы этого не хотите.
Я добавил этот ответ, потому что ни один ответ здесь, кажется, не показывает это различие, и поэтому они систематически полагаются на работающий контейнер.

Поэтому я думаю, что вы должны переместить это свойство, определенное как внутреннее свойство класса:

@Component
public class Foo{   
    @Value("${property.value}") private String property;
    //...
}

в параметр конструктора, который будет вставлен Spring:

@Component
public class Foo{   
    private String property;

    public Foo(@Value("${property.value}") String property){
       this.property = property;
    }

    //...         
}

Пример юнит-теста

Вы можете создать экземпляр Foo без Spring и ввести любое значение для property благодаря конструктору:

public class FooTest{

   Foo foo = new Foo("dummyValue");

   @Test
   public void doThat(){
      ...
   }
}

Пример интеграционного теста

Вы можете внедрить свойство в контексте Spring Boot таким простым способом благодаря атрибуту properties в @SpringBootTest:

@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
       ...
   }    
}

Вы можете использовать в качестве альтернативы @TestPropertySource, но он добавляет дополнительную аннотацию:

@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}

С Spring (без Spring Boot) все должно быть немного сложнее, но, поскольку я долгое время не использовал Spring без Spring Boot, я не предпочитаю говорить глупости.

В качестве примечания: если вам нужно установить много полей @Value, то их извлечение в класс, помеченный @ConfigurationProperties, более уместно, потому что нам не нужен конструктор со слишком большим количеством аргументов.

Ответ 5

Кажется, что это работает, хотя все еще немного многословно (я бы хотел еще что-то поменьше):

@BeforeClass
public static void beforeClass() {
    System.setProperty("some.property", "<value>");
}

// Optionally:
@AfterClass
public static void afterClass() {
    System.clearProperty("some.property");
}

Ответ 6

Добавление PropertyPlaceholderConfigurer в конфигурации работает для меня.

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class TestConfiguration {
    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        builder.setType(EmbeddedDatabaseType.DERBY);
        return builder.build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan(new String[] { "com.test.model" });
        // Use hibernate
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
        return entityManagerFactoryBean;
    }

    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.show_sql", "false");
        properties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
        properties.put("hibernate.hbm2ddl.auto", "update");
        return properties;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
         transactionManager.setEntityManagerFactory(
              entityManagerFactory().getObject()
         );

         return transactionManager;
    }

    @Bean
    PropertyPlaceholderConfigurer propConfig() {
        PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
        placeholderConfigurer.setLocation(new ClassPathResource("application_test.properties"));
        return placeholderConfigurer;
    }
}

И в тестовом классе

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
public class DataServiceTest {

    @Autowired
    private DataService dataService;

    @Autowired
    private DataRepository dataRepository;

    @Value("${Api.url}")
    private String baseUrl;

    @Test
    public void testUpdateData() {
        List<Data> datas = (List<Data>) dataRepository.findAll();
        assertTrue(datas.isEmpty());
        dataService.updateDatas();
        datas = (List<Data>) dataRepository.findAll();
        assertFalse(datas.isEmpty());
    }
}