Mùa xuân đông dân cư @Value trong bài kiểm tra đơn vị


238

Tôi đang cố gắng viết Bài kiểm tra đơn vị cho một loại đậu đơn giản được sử dụng trong chương trình của tôi để xác thực các biểu mẫu. Bean được chú thích @Componentvà có một biến lớp được khởi tạo bằng cách sử dụng

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

Tôi muốn viết các bài kiểm tra đơn vị cho các phương thức xác nhận bên trong lớp này, tuy nhiên, nếu có thể tôi muốn làm như vậy mà không sử dụng tệp thuộc tính. Lý do của tôi đằng sau điều này, là nếu giá trị tôi lấy từ tệp thuộc tính thay đổi, tôi muốn điều đó không ảnh hưởng đến trường hợp thử nghiệm của mình. Trường hợp thử nghiệm của tôi đang kiểm tra mã xác nhận giá trị, chứ không phải giá trị chính nó.

Có cách nào để sử dụng mã Java trong lớp thử nghiệm của tôi để khởi tạo một lớp Java và điền vào thuộc tính Spring @Value bên trong lớp đó sau đó sử dụng mã đó để kiểm tra không?

Tôi đã tìm thấy Cách làm này có vẻ gần, nhưng vẫn sử dụng tệp thuộc tính. Tôi muốn thay vì tất cả là mã Java.


Tôi đã mô tả một giải pháp ở đây cho vấn đề tương tự. Hy vọng nó giúp.
chân trời7

Câu trả lời:


199

Nếu có thể tôi sẽ cố gắng viết những bài kiểm tra đó mà không có Spring Context. Nếu bạn tạo lớp này trong bài kiểm tra của mình mà không có mùa xuân, thì bạn có toàn quyền kiểm soát các trường của nó.

Để đặt @valuetrường, bạn có thể sử dụng Springs ReflectionTestUtils- nó có một phương pháp setFieldđể đặt các trường riêng.

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


2
Chính xác những gì tôi đã cố gắng làm và những gì tôi đang tìm kiếm để thiết lập giá trị trong lớp học của tôi, cảm ơn!
Kyle

2
Hoặc thậm chí không có phụ thuộc Spring nào bằng cách thay đổi trường thành quyền truy cập mặc định (gói được bảo vệ) để làm cho nó có thể truy cập đơn giản để kiểm tra.
Arne Burmeister

22
Ví dụ:org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
Olivier

4
Bạn có thể muốn làm cho các trường này được thiết lập bởi hàm tạo và sau đó di chuyển @Valuechú thích sang tham số hàm tạo. Điều này làm cho mã kiểm tra đơn giản hơn nhiều khi viết mã thủ công và Spring Boot không quan tâm.
Thorbjørn Ravn Andersen

Đây là câu trả lời tốt nhất để chỉ nhanh chóng thay đổi một thuộc tính cho một testcase duy nhất.
viên

194

Kể từ Spring 4.1, bạn có thể thiết lập các giá trị thuộc tính chỉ bằng mã bằng cách sử dụng org.springframework.test.context.TestPropertySourcechú thích ở cấp độ Bài kiểm tra đơn vị. Bạn có thể sử dụng phương pháp này ngay cả để tiêm các thuộc tính vào các trường hợp bean phụ thuộc

Ví dụ

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

  }

}

Lưu ý: Cần có ví dụ về org.springframework.context.support.PropertySourcesPlaceholderConfigurerbối cảnh Mùa xuân

Chỉnh sửa 24-08-2017: Nếu bạn đang sử dụng SpringBoot 1.4.0 trở lên, bạn có thể khởi tạo các thử nghiệm với @SpringBootTest@SpringBootConfigurationchú thích. Thêm thông tin ở đây

Trong trường hợp SpringBoot, chúng tôi có mã sau

@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
Cảm ơn bạn, cuối cùng cũng có người trả lời cách ghi đè Giá trị chứ không phải cách đặt trường. Tôi lấy các giá trị từ trường chuỗi trong PostConstruct và vì vậy tôi cần giá trị chuỗi được đặt bởi Spring, không phải sau khi xây dựng.
tequilacat

@Value ("$ aaaa") - bạn có thể sử dụng cái này bên trong lớp Config không?
Kalpesh Soni

Tôi không chắc chắn vì cấu hình là lớp tĩnh. Nhưng xin vui lòng kiểm tra
Dmytro Boichenko

Làm cách nào tôi có thể sử dụng chú thích @Value trong lớp Kiểm tra Mockito?
dùng1575601

Tôi đang viết một bài kiểm tra tích hợp cho một dịch vụ không tham chiếu bất kỳ mã nào tìm nạp các giá trị từ tệp thuộc tính nhưng ứng dụng của tôi có lớp cấu hình đang tìm nạp giá trị từ tệp thuộc tính. Vì vậy, khi tôi đang chạy thử nghiệm, nó sẽ báo lỗi của trình giữ chỗ chưa được giải quyết, hãy nói "$ {spring.redis.port}"
huyền thoại

61

Đừng lạm dụng các lĩnh vực riêng tư nhận / thiết lập bởi sự phản ánh

Sử dụng sự phản chiếu như được thực hiện trong một số câu trả lời ở đây là điều mà chúng ta có thể tránh.
Nó mang lại một giá trị nhỏ ở đây trong khi nó thể hiện nhiều nhược điểm:

  • chúng tôi chỉ phát hiện các sự cố phản ánh trong thời gian chạy (ví dụ: các trường không còn tồn tại nữa)
  • Chúng tôi muốn đóng gói nhưng không phải là một lớp mờ che giấu các phụ thuộc cần nhìn thấy và làm cho lớp này mờ hơn và ít kiểm tra hơn.
  • nó khuyến khích thiết kế xấu. Hôm nay bạn tuyên bố a @Value String field. Ngày mai bạn có thể tuyên bố 5hoặc 10trong số họ trong lớp đó và bạn thậm chí có thể không nhận thức được rằng bạn giảm thiết kế của lớp. Với cách tiếp cận rõ ràng hơn để đặt các trường này (chẳng hạn như hàm tạo), bạn sẽ suy nghĩ hai lần trước khi thêm tất cả các trường này và có thể bạn sẽ gói chúng vào một lớp khác và sử dụng @ConfigurationProperties.

Làm cho lớp của bạn có thể kiểm tra cả đơn nhất và tích hợp

Để có thể viết cả các bài kiểm tra đơn vị đơn giản (không có bộ chứa lò xo đang chạy) và các bài kiểm tra tích hợp cho lớp thành phần Spring của bạn, bạn phải làm cho lớp này có thể sử dụng được có hoặc không có Spring.
Chạy một container trong một bài kiểm tra đơn vị khi không cần thiết là một cách làm tồi tệ làm chậm các bản dựng cục bộ: bạn không muốn điều đó.
Tôi đã thêm câu trả lời này vì không có câu trả lời nào ở đây dường như cho thấy sự khác biệt này và vì vậy họ dựa vào một container đang chạy một cách có hệ thống.

Vì vậy, tôi nghĩ rằng bạn nên di chuyển thuộc tính này được xác định là nội bộ của lớp:

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

vào một tham số hàm tạo sẽ được Spring chèn vào:

@Component
public class Foo{   
    private String property;

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

    //...         
}

Ví dụ kiểm tra đơn vị

Bạn có thể khởi tạo Foomà không cần Spring và thêm bất kỳ giá trị nào để propertycảm ơn nhà xây dựng:

public class FooTest{

   Foo foo = new Foo("dummyValue");

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

Ví dụ kiểm tra tích hợp

Bạn có thể tiêm thuộc tính trong ngữ cảnh với Spring Boot theo cách đơn giản này nhờ propertiesthuộc tính của @SpringBootTest :

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

   @Autowired
   Foo foo;

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

Bạn có thể sử dụng thay thế @TestPropertySourcenhưng nó thêm một chú thích bổ sung:

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

Với Spring (không có Spring Boot), nó sẽ phức tạp hơn một chút nhưng vì tôi đã không sử dụng Spring mà không có Spring Boot từ lâu nên tôi không thích nói một điều ngu ngốc.

Như một lưu ý phụ: nếu bạn có nhiều @Valuetrường để đặt, trích xuất chúng vào một lớp được chú thích có @ConfigurationPropertiesliên quan hơn vì chúng ta không muốn một hàm tạo có quá nhiều đối số.


Câu trả lời chính xác. Thực tiễn tốt nhất ở đây cũng là dành cho các trường khởi tạo của nhà xây dựng final, tức làprivate String final property
kugo2006

1
Thật tuyệt khi có ai đó nhấn mạnh điều đó. Để làm cho nó chỉ hoạt động với Spring, cần phải thêm lớp được kiểm tra trong @ContextConfiguration.
vimterd

53

Nếu bạn muốn, bạn vẫn có thể chạy thử nghiệm trong Bối cảnh mùa xuân và đặt các thuộc tính bắt buộc trong lớp cấu hình Spring. Nếu bạn sử dụng JUnit, hãy sử dụng SpringJUnit4ClassRunner và xác định lớp cấu hình chuyên dụng cho các thử nghiệm của bạn như thế:

Lớp đang kiểm tra:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

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

Lớp kiểm tra:

@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() { ... }
}

Và lớp cấu hình cho bài kiểm tra này:

@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);
    }
}

Có nói rằng, tôi sẽ không đề xuất phương pháp này, tôi chỉ cần thêm nó ở đây để tham khảo. Theo tôi cách tốt hơn nhiều là sử dụng người chạy Mockito. Trong trường hợp đó, bạn hoàn toàn không chạy thử nghiệm bên trong Spring, điều này rõ ràng và đơn giản hơn nhiều.


4
Tôi đồng ý rằng hầu hết logic nên được kiểm tra với Mockito. Tôi ước có một cách tốt hơn để kiểm tra sự hiện diện và tính chính xác của các chú thích hơn là chạy thử nghiệm qua Spring.
Altair7852

29

Điều này dường như hoạt động, mặc dù vẫn hơi dài dòng (Tôi muốn một cái gì đó ngắn hơn vẫn còn):

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

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

2
Tôi nghĩ rằng câu trả lời này sạch hơn vì nó là thuyết bất khả tri của mùa xuân, nó hoạt động tốt cho các tình huống khác nhau, như khi bạn phải sử dụng các bài kiểm tra tùy chỉnh và không thể chỉ thêm @TestPropertychú thích.
raspacorp

Điều này chỉ hoạt động cho phương pháp thử nghiệm tích hợp mùa xuân. Một số câu trả lời và nhận xét ở đây đang nghiêng về cách tiếp cận Mockito, vì điều này chắc chắn không hoạt động (vì Mockito sẽ không có gì trong đó @Value, bất kể tài sản tương ứng có được đặt hay không.
Sander Verhagen

5

Thêm PropertyPlaceholderConfigker trong cấu hình đang hoạt động với tôi.

@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;
    }
}

Và trong lớp kiểm tra

@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());
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.