簡體   English   中英

在單元測試期間填充 Spring @Value

[英]Populating Spring @Value during Unit Test

我正在嘗試為我的程序中用於驗證 forms 的簡單 bean 編寫單元測試。 該 bean 使用@Component進行注釋,並具有一個 class 變量,該變量使用初始化

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

我想為此 class 中的驗證方法編寫單元測試,但是,如果可能的話,我想在不使用屬性文件的情況下這樣做。 我的理由是,如果我從屬性文件中提取的值發生變化,我希望這不會影響我的測試用例。 我的測試用例是測試驗證值的代碼,而不是值本身。

Is there a way to use Java code inside my test class to initialize a Java class and populate the Spring @Value property inside that class then use that to test with?

我確實發現這個How To似乎很接近,但仍然使用屬性文件。 我寧願都是 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 的情況下使用 Spring,所以我不想說一件愚蠢的事情。

附帶說明:如果你有很多@Value字段要設置,將它們提取到一個用@ConfigurationProperties注釋的類中更相關,因為我們不想要一個帶有太多參數的構造函數。

如果需要,您仍然可以在 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);
    }
}

話雖如此,我不會推薦這種方法,我只是在這里添加它以供參考。 在我看來,更好的方法是使用 Mockito runner。 在這種情況下,您根本不會在 Spring 中運行測試,這會更加清晰和簡單。

這似乎有效,雖然仍然有點冗長(我想要更短的東西):

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

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

在配置中添加 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());
    }
}
@ExtendWith(SpringExtension.class)    // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)

可能會有所幫助。 關鍵是 ConfigDataApplicationContextInitializer 獲取所有道具數據

這是一個很老的問題,我不確定當時它是否是一個選項,但這就​​是為什么我總是更喜歡構造函數而不是值的 DependencyInjection 的原因。

我可以想象你的班級可能是這樣的:

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

通過這個實現,spring 將知道要自動注入什么,但是對於單元測試,你可以做任何你需要的。 例如使用spring自動裝配每個依賴項,並通過構造函數手動注入它們以創建“ExampleClass”實例,或者僅使用帶有測試屬性文件的spring,或者根本不使用spring並自己創建所有對象。

我使用了下面的代碼,它對我有用:

@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里面需要添加以下代碼_

@Test
public void testIsValidFile() {

    AnyClass anyClass = new AnyClass();
    ReflectionTestUtils.setField(anyClass, "fieldName", "value");
    .........
    .........
}

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 文檔,您會發現數以千計的操作系統方法可以做到這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM