在单元测试期间填充Spring @Value


238

我正在尝试为程序中用于验证表单的简单bean编写单元测试。Bean带有注释,@Component并具有使用初始化的类变量

@Value("${this.property.value}") private String thisProperty;

我想为此类中的验证方法编写单元测试,但是,如果可能的话,我希望在不利用属性文件的情况下这样做。我这样做的原因是,如果我从属性文件中提取的值发生更改,我希望这不会影响我的测试用例。我的测试用例正在测试验证值的代码,而不是值本身。

有没有一种方法可以在测试类中使用Java代码来初始化Java类,并在该类中填充Spring @Value属性,然后使用该属性进行测试?

我没有找到这个如何,这似乎是接近,但依然采用的是属性文件。我宁愿全部都是Java代码。


在这里描述了类似问题的解决方案。希望能帮助到你。
horizo​​n7

Answers:


199

如果可能的话,我将尝试在没有Spring Context的情况下编写那些测试。如果您在没有spring的测试中创建此类,则可以完全控制其字段。

要设置@value字段,您可以使用Springs- ReflectionTestUtils它具有setField设置私有字段的方法。

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


2
正是我想做的以及为在班级中设置值而一直在寻找的东西,谢谢!
凯尔

2
甚至根本没有Spring依赖项,只需将字段更改为默认访问权限(受程序包保护)即可使其可以轻松地通过测试访问。
Arne Burmeister

22
示例:org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
奥利维尔(Olivier)

4
您可能希望由构造函数设置这些字段,然后将@Value注释移至构造函数参数。这使手动编写代码时的测试代码更加简单,而Spring Boot则不在乎。
托尔比约恩Ravn的安德森

这是快速更改单个测试用例的一个属性的最佳答案。
–membersound

193

从Spring 4.1开始,您可以通过使用代码在代码中设置属性值 org.springframework.test.context.TestPropertySource在单元测试类级别上批注。您甚至可以将这种方法用于将属性注入到依赖的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
谢谢,最后有人回答了如何覆盖Value而不是如何设置字段。我从PostConstruct中的字符串字段派生值,因此我需要由Spring设置字符串值,而不是在构造之后。
tequilacat

@Value(“ $ aaaa”)-您可以在Config类本身中使用它吗?
Kalpesh Soni,

我不确定,因为Config是静态类。但请随时检查
Dmytro Boichenko '18年

如何在Mockito Test类中使用@Value注释?
user1575601 18-10-22

我正在为服务编写集成测试,该服务未引用任何从属性文件中获取值的代码,但是我的应用程序具有从属性文件中获取值的配置类。因此,当我运行测试时,它给出了无法解析的占位符的错误,请说“ $ {spring.redis.port}”
图例

63

不要滥用通过反射获得/设置的私有字段

我们可以避免使用反射,因为在几个答案中可以做到这一点。
它在这里带来很小的价值,但同时也带来了许多缺点:

  • 我们仅在运行时检测反射问题(例如:字段不再存在)
  • 我们需要封装,而不是一个不透明的类,它隐藏了应该是可见的依赖关系,并使该类更不透明且更不可测试。
  • 它鼓励不良的设计。今天您声明一个@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(){
      ...
   }
}

集成测试示例

由于具有以下properties属性,您可以使用Spring Boot以这种简单的方式在上下文中注入属性@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 Boot,所以我不喜欢说愚蠢的话。

附带说明:如果@Value要设置许多字段,则将它们提取到带有注解的类@ConfigurationProperties中更为相关,因为我们不希望构造函数具有太多参数。


好答案。最佳实践也是将构造函数初始化的字段设为final,即private String final property
kugo2006

1
很高兴有人强调了这一点。为了使其仅与Spring一起使用,有必要在@ContextConfiguration中添加被测类。
6

53

如果需要,您仍然可以在Spring Context中运行测试,并在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);
    }
}

话虽如此,我不推荐这种方法,我只是在这里添加它作为参考。在我看来,更好的方法是使用MockitoRunner。在那种情况下,您根本不会在Spring内部运行测试,这更加清晰和简单。


4
我同意应该使用Mockito测试大多数逻辑。我希望有比通过Spring运行测试更好的方法来测试注释的存在和正确性。
Altair7852

29

这似乎可行,尽管仍然有些冗长(我想再简短一些):

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

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

2
我认为这个答案比较干净,因为它与Spring无关,它适用于不同的场景,例如当您必须使用自定义测试运行程序并且不能仅添加@TestProperty注释时。
raspacorp '18

这仅适用于Spring集成测试方法。一些答案,并在这里评论朝的Mockito的方法,为此,这肯定是不行的(因为没有什么在的Mockito将填充倚@ValueS,不管相应的属性是否设置与否
桑德费尔哈亨

5

在配置中添加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());
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.