단위 테스트 중 스프링 @ 값 채우기


238

양식에서 유효성을 검사하기 위해 프로그램에서 사용되는 간단한 Bean에 대한 단위 테스트를 작성하려고합니다. Bean은 주석이 달린 @Component클래스 변수를 사용하여 초기화됩니다.

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

이 클래스 내에서 유효성 검사 메소드에 대한 단위 테스트를 작성하고 싶지만 가능하면 특성 파일을 사용하지 않고 수행하고 싶습니다. 이 뒤에 내 이유는 속성 파일에서 가져 오는 값이 변경되면 테스트 케이스에 영향을 미치지 않기를 원한다는 것입니다. 내 테스트 사례는 값 자체가 아닌 값의 유효성을 검사하는 코드를 테스트하는 것입니다.

테스트 클래스 내에서 Java 코드를 사용하여 Java 클래스를 초기화하고 해당 클래스 내부의 Spring @Value 속성을 채운 다음 테스트에 사용하는 방법이 있습니까?

How To 가 가깝다는 것을 알았지 만 여전히 속성 파일을 사용합니다. 오히려 그것은 모두 Java 코드입니다.


비슷한 문제에 대한 해결책을 여기 에 설명했습니다 . 도움이 되길 바랍니다.
horizon

답변:


199

가능하다면 Spring Context없이 테스트를 작성하려고합니다. 스프링없이 테스트에서이 클래스를 작성하면 해당 필드를 완전히 제어 할 수 있습니다.

@value필드 를 설정하기 위해 Springs를 사용할 수 있습니다 ReflectionTestUtils- setField개인 필드를 설정 하는 방법 이 있습니다.

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


2
정확히 내가하려고했던 것과 수업 내에서 가치를 설정하기 위해 찾고 있었던 것은 감사합니다!
Kyle

2
또는 스프링 의존성이 전혀없는 경우에도 필드를 기본 액세스 (패키지 보호)로 변경하여 테스트에 간단히 액세스 할 수 있도록합니다.
Arne Burmeister

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

4
생성자가이 필드를 설정 한 다음 @Value주석을 생성자 매개 변수 로 옮길 수 있습니다. 이렇게하면 코드를 수동으로 작성할 때 테스트 코드가 훨씬 간단 해지며 스프링 부트는 신경 쓰지 않습니다.
Thorbjørn Ravn Andersen

이것은 단일 테스트 케이스에 대해 하나의 특성을 빠르게 변경하는 가장 좋은 방법입니다.
membersound

194

Spring 4.1부터 org.springframework.test.context.TestPropertySourceUnit Tests 클래스 레벨의 주석을 사용하여 코드에서 속성 값을 설정할 수 있습니다. 종속 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();
    }

  }

}

참고 :org.springframework.context.support.PropertySourcesPlaceholderConfigurer Spring 컨텍스트에 인스턴스가 있어야합니다.

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

}

3
감사합니다. 마지막으로 누군가 필드를 설정하는 방법이 아닌 Value를 재정의하는 방법에 대답했습니다. PostConstruct의 문자열 필드에서 값을 파생하므로 생성 후가 아닌 Spring에서 문자열 값을 설정해야합니다.
tequilacat

@Value ( "$ aaaa")-클래스 Config 자체에서 사용할 수 있습니까?
Kalpesh Soni

구성이 정적 클래스이므로 확실하지 않습니다. 그러나 확인하시기 바랍니다
Dmytro Boichenko

Mockito Test 클래스에서 @Value 주석을 어떻게 사용할 수 있습니까?
user1575601

속성 파일에서 값을 가져 오는 코드를 참조하지 않는 서비스에 대한 통합 테스트를 작성하고 있지만 응용 프로그램에는 속성 파일에서 값을 가져 오는 구성 클래스가 있습니다. 따라서 테스트를 실행할 때 해결되지 않은 자리 표시 자 오류가 발생합니다. "$ {spring.redis.port}"
Legend

63

리플렉션으로 개인 필드를 가져 오거나 설정하지 마십시오.

여기에 몇 가지 답변에서 반사를 사용하면 피할 수 있습니다.
여기에는 작은 단점이 있지만 여러 가지 단점이 있습니다.

  • 런타임시에만 반사 문제를 감지합니다 (예 : 더 이상 존재하지 않는 필드)
  • 우리는 캡슐화를 원하지만 보이지 않는 종속성을 숨기고 클래스를 더 불투명하고 테스트하기 어려운 불투명 클래스는 아닙니다.
  • 나쁜 디자인을 장려합니다. 오늘은을 선언합니다 @Value String field. 내일 당신은 그 클래스에서 5또는 10그것들을 선언 할 수 있으며 , 당신은 클래스의 디자인을 줄인다는 것을 정확히 알지 못할 수도 있습니다. 이러한 필드 (생성자와 같은)를 설정하는보다 가시적 인 접근 방법을 사용하면 이러한 필드를 모두 추가하기 전에 두 번 생각하고 다른 클래스로 캡슐화하여 사용할 것 @ConfigurationProperties입니다.

수업을 단일 및 통합 테스트 가능

일반 단위 테스트 (스프링 컨테이너가 실행 중이 아님)와 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;
    }

    //...         
}

단위 테스트 예

FooSpring없이 인스턴스화 property하고 생성자 덕분에 값을 삽입 할 수 있습니다 .

public class FooTest{

   Foo foo = new Foo("dummyValue");

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

통합 테스트 예

다음과 같은 properties속성 덕분에 Spring Boot를 사용하여 컨텍스트에 속성을 주입 할 수 있습니다 @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{ ...}

Spring (Spring Boot없이)을 사용하면 조금 더 복잡해야하지만 오랫동안 Spring Boot없이 Spring을 사용하지 않았기 때문에 바보 같은 말을 선호하지 않습니다.

참고 사항 : @Value설정할 필드 가 많은 경우 주석이 달린 클래스로 필드를 추출하는 @ConfigurationProperties것이 더 관련이 있습니다. 인자가 너무 많은 생성자를 원하지 않기 때문입니다.


1
좋은 대답입니다. 여기에서 모범 사례는 생성자 초기화 필드를위한 것입니다 final.private String final property
kugo2006

1
누군가 그것을 강조한 것이 좋습니다. Spring에서만 작동하게하려면 @ContextConfiguration에서 테스트중인 클래스를 추가해야합니다.
vimterd

53

원하는 경우 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 내에서 테스트를 전혀 실행하지 않으므로 훨씬 명확하고 간단합니다.


4
나는 대부분의 로직을 Mockito로 테스트해야한다는 데 동의합니다. Spring을 통해 테스트를 실행하는 것보다 주석의 존재 및 정확성을 테스트하는 더 좋은 방법이 있었으면 좋겠다.
Altair7852

29

여전히 조금 장황하지만 작동하는 것 같습니다 (여전히 더 짧은 것을 원합니다).

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

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

2
나는이 대답이 Spring과 무관하기 때문에 더 깨끗하다고 ​​생각합니다. 맞춤형 테스트 러너를 사용해야하고 @TestProperty주석을 추가 할 수없는 경우와 같은 다른 시나리오에서 잘 작동합니다 .
raspacorp

이것은 스프링 통합 테스트 접근 방식에서만 작동합니다. 여기에 대한 일부 답변과 의견은 Mockito 접근 방식에 기대어 있습니다. Mockito @Value에는 해당 속성이 설정되어 있는지 여부에 관계없이 s 를 채우는 것이 없기 때문에 확실히 작동하지 않습니다.
Sander Verhagen

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());
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.