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

Kyl*_*yle 203 java junit spring spring-annotations

我正在尝试为我的程序中用于验证表单的简单bean编写单元测试.bean使用注释@Component并具有使用初始化的类变量@Value("${this.property.value}") private String thisProperty;

我想为这个类中的验证方法编写单元测试,但是,如果可能的话,我想在不使用属性文件的情况下这样做.我的理由是,如果我从属性文件中提取的值发生变化,我希望这不会影响我的测试用例.我的测试用例是测试验证值的代码,而不是值本身.

有没有办法在我的测试类中使用Java代码来初始化Java类并在该类中填充Spring @Value属性然后使用它来测试?

我确实发现这个How To看起来很接近,但仍然使用属性文件.我宁愿这一切都是Java代码.

谢谢

Ral*_*lph 180

如果可能,我会尝试在没有Spring Context的情况下编写这些测试.如果在没有弹簧的情况下在测试中创建此类,则可以完全控制其字段.

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

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

  • 示例:`org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest,"field","value");` (18认同)
  • 您可能希望由构造函数设置这些字段,然后将@Value注释移至构造函数参数。这使手动编写代码时的测试代码更加简单,而Spring Boot则不在乎。 (3认同)
  • 或者甚至没有Spring依赖性,通过将字段更改为默认访问(包受保护)以使其可以简单地访问测试. (2认同)

Dmy*_*nko 167

从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();
    }

  }

}
Run Code Online (Sandbox Code Playgroud)

注意:必须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);
  }

}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,最后有人回答了如何覆盖Value而不是如何设置字段.我从PostConstruct中的字符串字段派生值,因此我需要由Spring设置字符串值,而不是在构造之后. (3认同)

Luk*_*ski 51

如果需要,您仍然可以在Spring Context中运行测试,并在Spring配置类中设置所需的属性.如果您使用JUnit,请使用SpringJUnit4ClassRunner并为您的测试定义专用配置类:

被测试的课程:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

    @Value("${someProperty}")
    private String someProperty;
}
Run Code Online (Sandbox Code Playgroud)

测试类:

@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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

以及此测试的配置类:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

话虽如此,我不推荐这种方法,我只是在这里添加它以供参考.在我看来,更好的方法是使用Mockito跑步者.在这种情况下,你根本不在Spring内部运行测试,这更加清晰和简单.

  • 我同意大多数逻辑应该与Mockito一起测试.我希望有一种更好的方法来测试注释的存在性和正确性,而不是通过Spring运行测试. (4认同)

dav*_*xxx 39

使您的课程既可以单一也可以整合

为了能够为Spring组件类编写普通单元测试(没有运行弹簧容器)和集成测试,您必须使该类可以使用或不使用Spring.
在不需要的情况下在单元测试中运行容器是一种不好的做法,会降低本地构建的速度:您不希望这样做.
我添加了这个答案,因为这里的答案似乎没有显示出这种区别,所以他们系统地依赖于一个正在运行的容器.

所以我认为你应该将这个属性定义为类的内部:

@Component
public class Foo{   
    @Value("${property.value}") private String property;
    //...
}
Run Code Online (Sandbox Code Playgroud)

到将由Spring注入的构造函数参数:

@Component
public class Foo{   
    private String property;

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

    //...         
}
Run Code Online (Sandbox Code Playgroud)

单元测试示例

您可以在@Value String field没有Spring的情况下实例化并注入任何值以5感谢构造函数:

public class FooTest{

   Foo foo = new Foo("dummyValue");

   @Test
   public void doThat(){
      ...
   }
}
Run Code Online (Sandbox Code Playgroud)

集成测试示例

由于以下属性,您可以使用Spring Boot在上下文中注入10属性@ConfigurationProperties :

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

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
       ...
   }    
}
Run Code Online (Sandbox Code Playgroud)

您可以使用Foo它作为替代, 但它添加了一个额外的注释:

@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}
Run Code Online (Sandbox Code Playgroud)

使用Spring(没有Spring Boot),它应该有点复杂但是因为我很长一段时间没有使用没有Spring Boot的Spring,所以我不喜欢说一个愚蠢的事情.

  • 很好的答案。这里的最佳实践也是将构造函数初始化的字段设置为“final”,即“私有字符串最终属性” (3认同)
  • 很高兴有人强调了这一点。为了使其仅与Spring一起工作,需要在@ContextConfiguration中添加被测试的类。 (2认同)
  • @ACV 从来没有。请阅读我的回答的最后一句。如果要设置许多字段,配置属性类可以有设置器。对于仅被设计为属性持有者的类来说,这并不那么烦人。 (2认同)

joh*_*384 27

这似乎有效,虽然仍然有点冗长(我还想要更短的东西):

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

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

  • 这仅适用于 Spring 集成测试方法。这里的一些答案和评论倾向于 Mockito 方法,对于这种方法来说,这肯定行不通(因为 Mockito 中没有任何东西可以填充“@Value”,无论是否设置了相应的属性。 (3认同)
  • 我认为这个答案更清晰,因为它是Spring不可知的,它适用于不同的场景,比如当你必须使用自定义测试运行器而不能只添加`@ TestProperty`注释时. (2认同)

ahl*_*hll 10

@ExtendWith(SpringExtension.class)    // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
Run Code Online (Sandbox Code Playgroud)

愿这能有所帮助。关键是 ConfigDataApplicationContextInitializer 获取所有 props 数据


Prz*_*iec 6

这是一个相当老的问题,我不确定当时是否是一个选项,但这就​​是为什么我总是更喜欢通过构造函数而不是通过值进行 DependencyInjection 的原因。

我可以想象你的课程可能是这样的:

class ExampleClass{

   @Autowired
   private Dog dog;

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

   ...other stuff...
}
Run Code Online (Sandbox Code Playgroud)

您可以将其更改为:

class ExampleClass{

   private Dog dog;
   private String thisProperty;

   //optionally @Autowire
   public ExampleClass(final Dog dog, @Value("${this.property.value}") final String thisProperty){
      this.dog = dog;
      this.thisProperty = thisProperty;
   }

   ...other stuff...
}
Run Code Online (Sandbox Code Playgroud)

通过这个实现,spring将知道自动注入什么,但是对于单元测试,你可以做任何你需要的事情。例如,使用 spring 自动装配每个依赖项,并通过构造函数手动注入它们以创建“ExampleClass”实例,或者仅使用带有测试属性文件的 spring,或者根本不使用 spring 并自己创建所有对象。


小智 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在测试班

@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());
    }
}
Run Code Online (Sandbox Code Playgroud)