Birim Testi Sırasında @Değeri Doldurma


238

Formları doğrulamak için programımda kullanılan basit bir fasulye için Birim Testi yazmaya çalışıyorum. Fasulyeye açıklama eklenir @Componentve kullanılarak başlatılan bir sınıf değişkenine sahiptir.

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

Bu sınıf içindeki doğrulama yöntemleri için birim testleri yazmak istiyorum, ancak mümkünse bunu özellikler dosyasını kullanmadan yapmak istiyorum. Bunun arkasındaki mantığım, özellikler dosyasından çektiğim değer değişirse, test durumumu etkilememesini istiyorum. Test durumum, değeri doğrulayan kodu test ediyor, değerin kendisini değil.

Bir Java sınıfını başlatmak ve bu sınıfın içindeki Spring @Value özelliğini doldurmak için test kodumda Java kodu kullanmanın bir yolu var mı?

Bunu nasıl buldum Bu yakın gibi görünüyor, ama yine de bir özellikler dosyası kullanır. Ben tamamen Java kodu olmasını tercih ediyorum.


Burada benzer sorun için bir çözüm tanımladım . Umarım yardımcı olur.
horizon7

Yanıtlar:


199

Mümkünse bu testi Spring Context olmadan yazmaya çalışacağım. Bu sınıfı yaysız testinizde oluşturursanız, alanları üzerinde tam kontrole sahip olursunuz.

Alanı ayarlamak için @valueSprings'i kullanabilirsiniz ReflectionTestUtils- setFieldözel alanları ayarlamak için bir yöntemi vardır .

@ JavaDoc bakın: ReflectionTestUtils.setField (java.lang.Object, java.lang.String, java.lang.Object)


2
Tam olarak ne yapmaya çalışıyordum ve sınıfımın içindeki değeri ayarlamak için aradığım şey, teşekkürler!
Kyle

2
Veya Bahar bağımlılıkları olmadan bile, alanı teste kolayca erişilebilmesi için varsayılan erişime (paket korumalı) değiştirerek.
Arne Burmeister

22
Örnek:org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
Olivier

4
Bu alanları yapıcı tarafından ayarlanan yapmak ve @Valueek açıklamayı yapıcı parametresine taşımak isteyebilirsiniz. Bu, kodu manuel olarak yazarken test kodunu çok daha basit hale getirir ve Spring Boot umursamaz.
Thorbjørn Ravn Andersen

Bu, tek bir testcase için bir özelliği hızlı bir şekilde değiştirmek için en iyi yanıttır.
Üye

194

İlkbahar 4.1'den bu yana org.springframework.test.context.TestPropertySource, Birim Testleri sınıf düzeyinde ek açıklama kullanarak özellik değerlerini yalnızca kod olarak ayarlayabilirsiniz . Bağımlı fasulye örneklerine özellik enjekte etmek için bile bu yaklaşımı kullanabilirsiniz

Örneğin

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

  }

}

Not:org.springframework.context.support.PropertySourcesPlaceholderConfigurer Bahar bağlamında bir örneğe sahip olmak gerekir

Edit 24-08-2017: SpringBoot 1.4.0 ve üstünü kullanıyorsanız, testleri @SpringBootTestve @SpringBootConfigurationek açıklamaları başlatabilirsiniz. Daha fazla bilgi burada

SpringBoot durumunda aşağıdaki kodumuz var

@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
Teşekkür ederim, nihayet birisi bir alanın nasıl ayarlanacağını değil, Değerin nasıl geçersiz kılınacağını yanıtladı. PostConstruct dize alanından değerleri türetmek ve bu nedenle dize değeri Bahar tarafından ayarlandıktan sonra, inşaat sonra gerekir.
tequilacat

@Value ("$ aaaa") - bunu sınıf içi Config içinde kullanabilir misiniz?
Kalpesh Soni

Emin değilim çünkü Config statik sınıftır. Ama lütfen kontrol
etmekten

Mockito Test sınıfında @Value ek açıklamasını nasıl kullanabilirim?
user1575601

Özellik dosyasından değerleri getiren herhangi bir kodu referans vermeyen bir hizmet için bir tümleştirme testi yazıyorum ama benim uygulama özellik dosyasından değer alma yapılandırma sınıfı vardır. Bu yüzden testi çalıştırırken çözülmeyen yer tutucu hatası veriyor, "$ {spring.redis.port}" deyin
efsane

63

Özel alanları yansıma yoluyla alma / ayarlama

Burada birkaç cevapta yapılan yansımayı kullanmak, kaçınabileceğimiz bir şeydir.
Birden fazla dezavantaj sunarken buraya küçük bir değer getirir:

  • yansıma sorunlarını yalnızca çalışma zamanında tespit ederiz (ör: artık mevcut olmayan alanlar)
  • Kapsülleme istiyoruz, ancak görünür olması ve sınıfı daha opak ve daha az test edilebilir hale getirmesi gereken bağımlılıkları gizleyen opak bir sınıf değil.
  • kötü tasarımı teşvik eder. Bugün bir @Value String field. Yarın bu sınıfta ilan edebilir 5ya 10da onlardan ders verebilir ve sınıfın tasarımını azalttığınızın farkında bile olmayabilirsiniz. Bu alanları (oluşturucu gibi) ayarlamak için daha görünür bir yaklaşımla, tüm bu alanları eklemeden önce iki kez düşünecek ve muhtemelen onları başka bir sınıfa ve kullanıma sokacaksınız @ConfigurationProperties.

Sınıfınızı hem üniter hem de entegrasyonda test edilebilir yapın

Spring bileşen sınıfınız için hem düz ünite testlerini (çalışan bir yay kabı olmayan) hem de entegrasyon testlerini yazabilmek için, bu sınıfı Spring ile veya Spring olmadan kullanılabilir hale getirmeniz gerekir.
Gerekli olmadığında bir birim testinde kapsayıcı çalıştırmak, yerel yapıları yavaşlatan kötü bir uygulamadır: bunu istemezsiniz.
Bu cevabı ekledim çünkü burada hiçbir cevap bu ayrımı göstermiyor gibi görünüyor ve bu nedenle sistematik olarak çalışan bir konteynere güveniyorlar.

Bu yüzden sınıfın bir iç olarak tanımlanan bu özelliği taşımak gerektiğini düşünüyorum:

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

Spring tarafından enjekte edilecek bir yapıcı parametresine:

@Component
public class Foo{   
    private String property;

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

    //...         
}

Birim test örneği

Yaysız başlatabilir ve kurucu sayesinde Fooherhangi bir değer enjekte edebilirsiniz property:

public class FooTest{

   Foo foo = new Foo("dummyValue");

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

Entegrasyon testi örneği

Sen bu basit bir yolu sayesinde Bahar Boot ile bağlamda mülkiyet enjekte edebilir propertiesoznıtelığı @SpringBootTest :

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

   @Autowired
   Foo foo;

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

Alternatif olarak kullanabilirsiniz, @TestPropertySourceancak ek bir ek açıklama ekler:

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

Spring ile (Spring Boot olmadan), biraz daha karmaşık olmalı ama Spring Boot olmadan Spring'i uzun zamandır kullanmadığım için aptalca bir şey söylemeyi tercih etmiyorum.

Bir yan not olarak: @Valueayarlanacak çok alanınız varsa , ek açıklama eklenmiş bir sınıfa ayıklamak @ConfigurationPropertiesdaha önemlidir, çünkü çok fazla bağımsız değişkeni olan bir yapıcı istemiyoruz.


1
Mükemmel cevap. Buradaki en iyi uygulama, yapıcı tarafından başlatılan alanların da olması içindir final, yaniprivate String final property
kugo2006

1
Birinin bunu vurgulaması güzel. Yalnızca Spring ile çalışmasını sağlamak için, test altındaki sınıfı @ContextConfiguration içine eklemek gerekir.
vimterd

53

İsterseniz, testlerinizi Spring Context içinde çalıştırabilir ve Spring configuration sınıfında gerekli özellikleri ayarlayabilirsiniz. JUnit kullanıyorsanız, SpringJUnit4ClassRunner kullanın ve testleriniz için özel yapılandırma sınıfı tanımlayın:

Test edilen sınıf:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

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

Test sınıfı:

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

Ve bu test için yapılandırma sınıfı:

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

Bunu söyledikten sonra, bu yaklaşımı tavsiye etmem, referans için buraya ekledim. Bence çok daha iyi bir yol Mockito koşucusu kullanmak. Bu durumda, Bahar içinde hiç test yapmazsınız, bu da çok daha açık ve basittir.


4
Çoğu mantığın Mockito ile test edilmesi gerektiğine katılıyorum. Ek açıklamaların varlığını ve doğruluğunu test etmenin Bahar boyunca test yapmaktan daha iyi bir yolu olmasını isterdim.
Altair7852

29

Yine de biraz ayrıntılı olmasına rağmen bu işe yarıyor gibi görünüyor (daha kısa bir şey istiyorum):

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

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

2
Bu cevap Bahar agnostik olduğu için daha temiz olduğunu düşünüyorum, farklı test senaryoları kullanmanız gerektiğinde ve yalnızca @TestPropertyek açıklama ekleyemediğinizde olduğu gibi farklı senaryolar için iyi çalışıyor .
raspacorp

Bu sadece Bahar entegrasyon testi yaklaşımı için geçerlidir. Buradaki bazı cevaplar ve yorumlar, bunun kesinlikle işe yaramadığı bir Mockito yaklaşımına yöneliktir ( @Valueilgili mülkün ayarlanıp ayarlanmadığına bakılmaksızın Mockito'da s'yi dolduracak hiçbir şey olmadığından
Sander Verhagen

5

Yapılandırmaya PropertyPlaceholderConfigurer eklemek benim için çalışıyor.

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

Ve test sınıfında

@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());
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.