在单元测试期间填充弹簧@Value



我正在尝试为一个简单的 bean 编写一个单元测试,在我的程序中用于验证表单。Bean 用 @Component 注释,并具有使用

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

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

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

我确实发现这个操作方法似乎很接近,但仍然使用属性文件。我宁愿这一切都是Java代码。

如果

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

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

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

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

注意:在 Spring 上下文中必须有 org.springframework.context.support.PropertySourcesPlaceholderConfigurer 的实例

编辑 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);
  }
}

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

在这里的几个答案中使用反射是我们可以避免的.
它在这里带来了很小的价值,但它存在多个缺点:

  • 我们仅在运行时检测反射问题(例如:字段不再存在(
  • 我们想要封装,而不是一个隐藏应该可见的依赖项并使类更不透明和更难测试的不透明类。
  • 它鼓励糟糕的设计。 今天你宣布@Value String field.明天你可以在该类中声明其中的510个,你甚至可能没有直接意识到你减少了类的设计。使用更明显的方法来设置这些字段(例如构造函数(,在添加所有这些字段之前,您会三思而后行,您可能会将它们封装到另一个类中并使用 @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;
    }
    //...         
}

单元测试示例

您可以在没有 Spring 的情况下实例化Foo并为property注入任何值,这要归功于构造函数:

public class FooTest{
   Foo foo = new Foo("dummyValue");
   @Test
   public void doThat(){
      ...
   }
}

集成测试示例

由于 @SpringBootTestproperties 属性,您可以通过这种简单的方式使用 Spring Boot 在上下文中注入属性:

@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{
    
   @Autowired
   Foo foo;
     
   @Test
   public void doThat(){
       ...
   }    
}

您可以用作替代@TestPropertySource但它增加了一个额外的注释:

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

使用Spring(没有Spring Boot(,它应该有点复杂,但是由于我很长一段时间没有使用Spring Boot,我不喜欢说一件愚蠢的事情。

作为旁注:如果您要设置许多@Value字段,将它们提取到带有 @ConfigurationProperties 注释的类中更相关,因为我们不想要具有太多参数的构造函数。

如果需要,您仍然可以在 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 中运行测试,这更加清晰和简单。

这似乎有效,尽管仍然有点冗长(我想要更短的内容(:

@BeforeClass
public static void beforeClass() {
    System.setProperty("some.property", "<value>");
}
// Optionally:
@AfterClass
public static void afterClass() {
    System.clearProperty("some.property");
}
@ExtendWith(SpringExtension.class)    // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)

但愿这会有所帮助。键是 ConfigDataApplicationContextInitializer 获取所有 props datas

这是一个相当古老的问题,我不确定当时它是否是一个选项,但这就是为什么我总是更喜欢构造函数而不是值的依赖注入的原因。

我可以想象你的班级可能是这样的:

class ExampleClass{
   @Autowired
   private Dog dog;
   @Value("${this.property.value}") 
   private String thisProperty;
   ...other stuff...
}

您可以将其更改为:

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...
}

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

在配置中添加 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());
    }
}

我使用了下面的代码,它对我有用:

@InjectMocks
private ClassNotify classNotify;
@BeforeEach
  void init() {
    closeable = MockitoAnnotations.openMocks(this);
    ReflectionTestUtils.setField(classNotify, "EventType", "test-event");
  }

在 springboot 2.4.1 中,我只是在我的测试中添加了注释@SpringBootTest,显然,在我的src/test/resources/application.yml中设置了spring.profiles.active = test

我使用@ExtendWith({SpringExtension.class})@ContextConfiguration(classes = {RabbitMQ.class, GenericMapToObject.class, ModelMapper.class, StringUtils.class})进行外部会议

test method内部需要添加以下code_

@Test
public void testIsValidFile() {
    AnyClass anyClass = new AnyClass();
    ReflectionTestUtils.setField(anyClass, "fieldName", "value");
    .........
    .........
}

我遇到了类似的问题。事实证明,一些以前的开发人员创建的测试类使用的是Mockito而不是Spring Boot Test。我删除了扩展方式,并用@MockBean和@SpyBean替换了@Mock和@Spy注释,因此测试变成了真正的春季启动测试。这解决了问题。

Spring

Boot 会自动对我们做很多事情,但是当我们使用注释@SpringBootTest时,我们认为 Spring boot 会自动解决所有问题。

有很多文档,但最少的是选择一个引擎(@RunWith(SpringRunner.class)(并指示将使用的类创建上下文以加载配置(resources/applicationl.properties(。

简单来说,你需要引擎上下文

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyClassTest .class)
public class MyClassTest {
    @Value("${my.property}")
    private String myProperty;
    @Test
    public void checkMyProperty(){
        Assert.assertNotNull(my.property);
    }
}

当然,如果你看一下Spring Boot文档,你会发现成千上万的方法可以做到这一点。

最新更新