यूनिट टेस्ट के दौरान वसंत @Value को आबाद करना


238

मैं एक साधारण बीन के लिए एक यूनिट टेस्ट लिखने की कोशिश कर रहा हूं जो मेरे प्रोग्राम में फॉर्म को मान्य करने के लिए उपयोग किया जाता है। सेम को एनोटेट किया गया है @Componentऔर इसका एक वर्ग चर है जिसका उपयोग करके आरंभ किया गया है

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

मैं इस वर्ग के अंदर सत्यापन विधियों के लिए यूनिट परीक्षण लिखना चाहूंगा, हालांकि, यदि संभव हो तो मैं गुण फ़ाइल का उपयोग किए बिना ऐसा करना चाहूंगा। इसके पीछे मेरा तर्क यह है कि अगर मैं जिस गुण को फाइल से खींच रहा हूं, उसका मूल्य बदल जाता है, तो मैं चाहूंगा कि मेरे परीक्षण मामले को प्रभावित न करें। मेरा परीक्षण मामला उस कोड का परीक्षण कर रहा है जो मूल्य को मान्य करता है, न कि मूल्य को।

क्या जावा क्लास को इनिशियलाइज़ करने के लिए मेरे टेस्ट क्लास के अंदर जावा कोड का उपयोग करने का एक तरीका है और उस क्लास के अंदर स्प्रिंग @ वेल्यू की संपत्ति को पॉप्युलेट करना है तो टेस्ट करने के लिए उपयोग करें?

मुझे यह पता चला कि यह कैसे बंद होता है, लेकिन अभी भी एक गुण फ़ाइल का उपयोग करता है। मैं नहीं बल्कि यह सब जावा कोड होगा।


मैंने इसी तरह की समस्या के लिए यहां एक समाधान का वर्णन किया है। आशा करता हूँ की ये काम करेगा।
क्षितिज 7

जवाबों:


199

यदि संभव हो तो मैं स्प्रिंग कॉन्सेप्ट के बिना उन टेस्ट को लिखने की कोशिश करूंगा। यदि आप वसंत के बिना अपने परीक्षण में इस वर्ग का निर्माण करते हैं, तो आपके पास इसके क्षेत्रों पर पूर्ण नियंत्रण है।

@valueफ़ील्ड सेट करने के लिए आप स्प्रिंग्स का उपयोग कर सकते हैं ReflectionTestUtils- इसमें setFieldनिजी फ़ील्ड सेट करने की एक विधि है।

@ जावा


2
वास्तव में मैं क्या करने की कोशिश कर रहा था और मैं अपनी कक्षा के अंदर मूल्य निर्धारित करने के लिए क्या देख रहा था, धन्यवाद!
काइल

2
या यहां तक ​​कि वसंत निर्भरता के बिना क्षेत्र को डिफ़ॉल्ट पहुंच (पैकेज संरक्षित) में बदलकर इसे केवल परीक्षण के लिए सुलभ बनाया जा सकता है।
अर्ने बर्मिस्टर

22
उदाहरण:org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
ओलिवियर

4
आप इन फ़ील्ड्स को कंस्ट्रक्टर द्वारा सेट करना चाहते हैं और फिर @Valueएनोटेशन को कंस्ट्रक्टर पैरामीटर पर ले जा सकते हैं। मैन्युअल रूप से कोड लिखते समय यह परीक्षण कोड को बहुत सरल बनाता है, और स्प्रिंग बूट परवाह नहीं करता है।
थोर्बजोरन रावन एंडरसन

यह एकल टेस्टकेस के लिए एक संपत्ति को जल्दी से बदलने का सबसे अच्छा जवाब है।
11:24

194

स्प्रिंग 4.1 के बाद से आप org.springframework.test.context.TestPropertySourceयूनिट टेस्ट क्लास स्तर पर एनोटेशन का उपयोग करके कोड में संपत्ति मूल्यों को स्थापित कर सकते हैं । आप इस दृष्टिकोण का उपयोग निर्भर बीन उदाहरणों में गुणों को इंजेक्ट करने के लिए भी कर सकते हैं

उदाहरण के लिए

@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 स्प्रिंग संदर्भ में उदाहरण का होना आवश्यक है

24-08-2017 को संपादित करें: यदि आप स्प्रिंगबूट 1.4.0 का उपयोग कर रहे हैं और बाद में आप के साथ परीक्षण शुरू कर सकते हैं @SpringBootTestऔर@SpringBootConfiguration एनोटेशन। और जानकारी यहाँ

स्प्रिंगबूट के मामले में हमारे पास निम्नलिखित कोड हैं

@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
धन्यवाद, अंत में किसी ने उत्तर दिया कि कैसे मूल्य को ओवरराइड किया जाए और न कि किसी फ़ील्ड को कैसे सेट किया जाए। मैं PostConstruct में स्ट्रिंग फ़ील्ड से मान प्राप्त करता हूं और इसलिए मुझे स्प्रिंग द्वारा सेट किए जाने वाले स्ट्रिंग मान की आवश्यकता है, न कि निर्माण के बाद।
टकीलाकैट

@Value ("$ aaaa") - क्या आप इसका उपयोग कक्षा के अंदर ही कर सकते हैं?
कल्पेश सोनी

मुझे यकीन नहीं है क्योंकि विन्यास स्थिर वर्ग है। लेकिन कृपया बेझिझक जांच करें
Dmytro Boichenko

मैं मॉकिटो टेस्ट क्लास में @Value एनोटेशन का उपयोग कैसे कर सकता हूं?
user1575601

मैं एक सेवा के लिए एक एकीकरण परीक्षण लिख रहा हूं जो किसी भी कोड को संदर्भित नहीं करता है जो संपत्ति फ़ाइल से मान प्राप्त करता है लेकिन मेरे आवेदन में कॉन्फ़िगरेशन वर्ग है जो संपत्ति फ़ाइल से मूल्य प्राप्त कर रहा है। इसलिए जब मैं परीक्षण कर रहा हूं तो यह अनियंत्रित प्लेसहोल्डर की त्रुटि दे रहा है, "$ {spring.redis.port}" कहें
पौराणिक कथा

63

दुरुपयोग न करें निजी क्षेत्र प्रतिबिंब द्वारा निर्धारित / निर्धारित करते हैं

परावर्तन का उपयोग करना जैसा कि कई उत्तरों में किया गया है, कुछ ऐसा है जिससे हम बच सकते हैं।
यह एक छोटी सी कीमत यहाँ लाता है जबकि यह कई कमियाँ प्रस्तुत करता है:

  • हम केवल रनटाइम पर प्रतिबिंब मुद्दों का पता लगाते हैं (उदा: फ़ील्ड अब मौजूद नहीं हैं)
  • हम एन्कैप्सुलेशन चाहते हैं लेकिन एक अपारदर्शी वर्ग नहीं जो निर्भरता को छुपाता है जो दिखाई देनी चाहिए और कक्षा को अधिक अपारदर्शी और कम परीक्षण योग्य बनाना चाहिए।
  • यह खराब डिजाइन को प्रोत्साहित करता है। आज आप एक घोषणा करते हैं @Value String field। कल आप उन्हें 5या 10उस कक्षा में घोषित कर सकते हैं और आपको शायद यह भी पता नहीं होगा कि आप कक्षा के डिजाइन को कम करते हैं। इन फ़ील्ड्स (जैसे कंस्ट्रक्टर) को सेट करने के लिए एक अधिक दृश्यमान दृष्टिकोण के साथ, आप इन सभी फ़ील्ड्स को जोड़ने से पहले दो बार सोचेंगे और आप संभवतः उन्हें किसी अन्य वर्ग में उपयोग करेंगे और उपयोग करेंगे @ConfigurationProperties

अपनी कक्षा को एकात्मक और एकीकरण दोनों में परीक्षण योग्य बनाएं

अपने स्प्रिंग कंपोनेंट क्लास के लिए दोनों सादे यूनिट टेस्ट (जो कि एक रनिंग स्प्रिंग कंटेनर के बिना है) और इंटीग्रेशन टेस्ट लिखने में सक्षम होने के लिए, आपको इस क्लास को स्प्रिंग के साथ या उसके बिना उपयोग करने योग्य बनाना होगा।
एक इकाई परीक्षण में कंटेनर को चलाने की आवश्यकता नहीं होने पर यह एक बुरा अभ्यास है जो स्थानीय बिल्ड को धीमा कर देता है: आप ऐसा नहीं चाहते हैं।
मैंने यह उत्तर इसलिए जोड़ा क्योंकि यहाँ कोई भी उत्तर इस भेद को नहीं दिखाता है और इसलिए वे व्यवस्थित रूप से एक चल रहे कंटेनर पर भरोसा करते हैं।

इसलिए मुझे लगता है कि आपको इस गुण को वर्ग के आंतरिक रूप में परिभाषित करना चाहिए:

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

एक निर्माण पैरामीटर में जिसे वसंत द्वारा इंजेक्ट किया जाएगा:

@Component
public class Foo{   
    private String property;

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

    //...         
}

इकाई परीक्षण उदाहरण

आप Fooस्प्रिंग के बिना इंस्टेंट कर सकते हैं और propertyकंस्ट्रक्टर को धन्यवाद के लिए किसी भी मूल्य को इंजेक्ट कर सकते हैं :

public class FooTest{

   Foo foo = new Foo("dummyValue");

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

एकीकरण परीक्षण उदाहरण

आप को यह आसान तरीका धन्यवाद में स्प्रिंग बूट के साथ संदर्भ में संपत्ति इंजेक्शन लगाने के कर सकते हैं propertiesकी विशेषता @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{ ...}

स्प्रिंग के साथ (स्प्रिंग बूट के बिना), यह थोड़ा और अधिक जटिल होना चाहिए लेकिन जैसा कि मैंने लंबे समय से स्प्रिंग बूट के बिना स्प्रिंग का उपयोग नहीं किया था, मैं एक बेवकूफ बात कहना पसंद नहीं करता।

एक साइड नोट के रूप में: यदि आपके पास @Valueसेट करने के लिए कई फ़ील्ड हैं, तो उन्हें एनोटेट के साथ वर्ग में निकालना @ConfigurationPropertiesअधिक प्रासंगिक है क्योंकि हम बहुत सारे तर्कों के साथ एक निर्माता नहीं चाहते हैं।


1
बहुत बढ़िया जवाब। यहाँ पर सबसे अच्छा अभ्यास निर्माण-आरंभीकृत क्षेत्रों के लिए भी है final, अर्थातprivate String final property
kugo2006

1
यह अच्छा है कि किसी ने उस पर प्रकाश डाला। केवल वसंत के साथ काम करने के लिए, @ContextConfiguration में परीक्षण के तहत कक्षा को जोड़ना आवश्यक है।
14

53

यदि आप चाहें, तो आप अभी भी स्प्रिंग कॉन्टेक्स्ट के भीतर अपने परीक्षण चला सकते हैं और स्प्रिंग कॉन्फ़िगरेशन क्लास के अंदर आवश्यक गुण सेट कर सकते हैं। यदि आप 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);
    }
}

उस ने कहा, मैं इस दृष्टिकोण की सिफारिश नहीं करूंगा, मैंने इसे केवल संदर्भ के लिए यहां जोड़ा है। मेरी राय में मॉकिटो रनर का उपयोग करना बेहतर तरीका है। उस स्थिति में आप स्प्रिंग के अंदर परीक्षण बिल्कुल नहीं चलाते हैं, जो कि अधिक स्पष्ट और सरल है।


4
मैं इस बात से सहमत हूं कि अधिकांश तर्क का मॉकिटो के साथ परीक्षण किया जाना चाहिए। मैं चाहता हूं कि स्प्रिंग के माध्यम से परीक्षण चलाने की तुलना में परीक्षण उपस्थिति और एनोटेशन की शुद्धता का एक बेहतर तरीका था।
Altair7852 10

29

यह काम करने लगता है, हालाँकि अभी भी थोड़ी सी क्रिया है (मुझे अभी भी कुछ कम पसंद है):

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

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

2
मुझे लगता है कि यह उत्तर क्लीनर है क्योंकि यह वसंत अज्ञेयवादी है, यह विभिन्न परिदृश्यों के लिए अच्छी तरह से काम करता है, जैसे कि जब आपको कस्टम परीक्षण धावक का उपयोग करना होता है और केवल @TestPropertyएनोटेशन नहीं जोड़ सकता है ।
रास्पाकॉर्प

यह केवल स्प्रिंग इंटीग्रेशन टेस्ट दृष्टिकोण के लिए काम करता है। यहाँ कुछ उत्तर और टिप्पणियाँ मॉकिटो दृष्टिकोण की ओर झुक रही हैं, जिसके लिए यह निश्चित रूप से काम नहीं करता है (क्योंकि मॉकिटो में ऐसा कुछ भी नहीं है जो एस को आबाद करेगा @Value, भले ही संबंधित संपत्ति सेट हो या न हो।
सैंडर वेरगन

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());
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.