복붙노트

[SPRING] mockito의 Spring 값 주입

SPRING

mockito의 Spring 값 주입

다음 메서드에 대한 테스트 클래스를 작성하려고합니다.

public class CustomServiceImpl implements CustomService {
    @Value("#{myProp['custom.url']}")
    private String url;
    @Autowire
    private DataService dataService;

클래스의 메서드 중 하나에서 삽입 된 URL 값을 사용하고 있습니다. 이 테스트하려면 junit 클래스를 작성했습니다.

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public CustomServiceTest{
    private CustomService customService;
    @Mock
    private DataService dataService;
    @Before
    public void setup() {
        customService = new CustomServiceImpl();
        Setter.set(customService, "dataService", dataService);
    }    
    ...
}

public class Setter {
    public static void set(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

applicationContext-test.xml에서 다음을 사용하여 속성 파일을로드하고 있습니다.

    <util:properties id="myProp" location="myProp.properties"/>

그러나 URL 값은 테스트 실행시 CustomService에로드되지 않습니다. 나는이 일을 끝내기 위해 어쨌든 궁금 해서요.

감사

해결법

  1. ==============================

    1.private 필드에 주석을다는 것보다 mutator (setter)에 autowire 할 수 있습니다. 그런 다음 테스트 클래스의 해당 설정기를 사용할 수도 있습니다. 그것을 public으로 만들 필요는 없으며, private 패키지는 Spring이 여전히 접근 할 수 있기 때문에 할 것입니다. 그러나 테스트 만이 거기에 들어갈 수 있습니다 (또는 같은 패키지의 다른 코드).

    private 필드에 주석을다는 것보다 mutator (setter)에 autowire 할 수 있습니다. 그런 다음 테스트 클래스의 해당 설정기를 사용할 수도 있습니다. 그것을 public으로 만들 필요는 없으며, private 패키지는 Spring이 여전히 접근 할 수 있기 때문에 할 것입니다. 그러나 테스트 만이 거기에 들어갈 수 있습니다 (또는 같은 패키지의 다른 코드).

    @Value("#{myProp['custom.url']}")
    String setUrl( final String url ) {
        this.url  = url;
    }
    

    필자는 테스트를 위해 (코드베이스와 비교하여) 다르게 autowiring하는 팬이 아니지만 테스트에서 테스트 할 클래스를 변경하는 대안은 단순히 성가시다.

  2. ==============================

    2.

    import org.springframework.test.util.ReflectionTestUtils;
    
    @RunWith(MockitoJUnitRunner.class)
    public CustomServiceTest{
    
    @InjectMock
    private CustomServiceImpl customService;
    
    @Mock
    private DataService dataService;
    
    @Before
    public void setup() {
        ReflectionTestUtils.setField(customService, "url", "http://someurl");
    }    
    ...
    }
    
  3. ==============================

    3.@ skaffman의 의견에 동의합니다.

    @ skaffman의 의견에 동의합니다.

    당신의 테스트는 MockitoJUnitRunner를 사용하는 것 외에 어떤 스프링 스터드도 찾지 않을 것입니다.이 유일한 목적은 Mockito 모의 객체를 초기화하는 것입니다. ContextConfiguration은 스프링으로 물건을 연결하기에 충분하지 않습니다. 기술적으로 JUnit을 사용하면 Spring 관련 항목 SpringJUnit4ClassRunner를 원하면 다음 주자를 사용할 수 있습니다.

    또한 Unit Test를 작성할 때 스프링 사용을 재검토하고 싶을 수도 있습니다. 단위 테스트에서 스프링 배선을 사용하는 것은 잘못되었습니다. 그러나 당신이 대신 통합 테스트를 쓰고 있다면 왜 Mockito를 사용하고 있습니까? (스카프 만이 말한 것처럼) 이해가 가지 않습니다!

    편집 : 이제 귀하의 코드를 직접 귀하의 이전 블록에 CustomerServiceImpl을 설정, 그건 이해가되지 않습니다. 봄은 거기에 전혀 관여하지 않습니다!

    @Before
    public void setup() {
        customService = new CustomServiceImpl();
        Setter.set(customService, "dataService", dataService);
    }
    

    편집 2 : CustomerServiceImpl의 단위 테스트를 작성하려면 스프링 물건을 피하고 직접 속성 값을 주입하십시오. 또한 Mockito를 사용하여 DataService 모의 테스트를 테스트 된 인스턴스에 삽입 할 수 있습니다.

    @RunWith(MockitoJUnitRunner.class)
    public CustomServiceImplTest{
        @InjectMocks private CustomServiceImpl customService;
        @Mock private DataService dataService;
    
        @Before void inject_url() { customerServiceImpl.url = "http://..."; }
    
        @Test public void customerService_should_delegate_to_dataService() { ... }
    }
    

    url 필드에 대한 직접 액세스를 사용하고 있음을 눈치 챘을 수도 있으므로이 필드는 패키지로 표시 될 수 있습니다. 이것은 Mockito가 모의 (mock) 만 주입하기 때문에 실제로 URL 값을 주입하는 테스트 해결 방법입니다.

  4. ==============================

    4.당신은 시험하려는 것을 조롱하면 안됩니다. 테스트하려는 코드를 건드리지 않으려 고하기 때문에 무의미합니다. 대신 컨텍스트에서 CustomerServiceImpl의 인스턴스를 가져옵니다.

    당신은 시험하려는 것을 조롱하면 안됩니다. 테스트하려는 코드를 건드리지 않으려 고하기 때문에 무의미합니다. 대신 컨텍스트에서 CustomerServiceImpl의 인스턴스를 가져옵니다.

  5. ==============================

    5.나는 특성 파일에서 읽는 끈의 명부가 있었다. @Before 블록에서 사용 된 ReflectionTestUtils 클래스의 setField 메소드는 테스트 실행 전에이 값들을 설정할 수 있도록 도와주었습니다. Common DaoSupport 클래스에 의존하는 나의 DAO 레이어에도 완벽하게 작동했습니다.

    나는 특성 파일에서 읽는 끈의 명부가 있었다. @Before 블록에서 사용 된 ReflectionTestUtils 클래스의 setField 메소드는 테스트 실행 전에이 값들을 설정할 수 있도록 도와주었습니다. Common DaoSupport 클래스에 의존하는 나의 DAO 레이어에도 완벽하게 작동했습니다.

    @Before
    public void setList() {
        List<String> mockedList = new ArrayList<>();
        mockedSimList.add("CMS");
        mockedSimList.add("SDP");
        ReflectionTestUtils.setField(mockedController, "ActualListInController",
                mockedList);
    }
    
  6. ==============================

    6.이 작은 유틸리티 클래스 (요점)를 사용하여 자동으로 필드 값을 대상 클래스에 삽입 할 수 있습니다.

    이 작은 유틸리티 클래스 (요점)를 사용하여 자동으로 필드 값을 대상 클래스에 삽입 할 수 있습니다.

    public class ValueInjectionUtils {
      private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
      private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
      private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER =
          new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX,
              SystemPropertyUtils.VALUE_SEPARATOR, true);
    
      public static void injectFieldValues(Object testClassInstance, Properties properties) {
        for (Field field : FieldUtils.getFieldsListWithAnnotation(testClassInstance.getClass(), Value.class)) {
          String value = field.getAnnotation(Value.class).value();
          if (value != null) {
            try {
              Object resolvedValue = resolveValue(value, properties);
              FieldUtils.writeField(field, testClassInstance, CONVERSION_SERVICE.convert(resolvedValue, field.getType()),
                  true);
            } catch (IllegalAccessException e) {
              throw new IllegalStateException(e);
            }
          }
        }
      }
    
      private static Object resolveValue(String value, Properties properties) {
        String replacedPlaceholderString = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(value, properties);
        return evaluateSpEL(replacedPlaceholderString, properties);
      }
    
      private static Object evaluateSpEL(String value, Properties properties) {
        Expression expression = EXPRESSION_PARSER.parseExpression(value, new TemplateParserContext());
        EvaluationContext context =
            SimpleEvaluationContext.forPropertyAccessors(new MapAccessor()).withRootObject(properties).build();
        return expression.getValue(context);
      }
    }
    

    org.apache.commons.lang3.reflect.FieldUtils를 사용하여 @Value로 주석 된 모든 필드에 액세스 한 다음 Spring 유틸리티 클래스를 사용하여 모든 자리 표시 자 값을 해결합니다. 자신의 PlaceholderResolver를 사용하려는 경우 매개 변수 속성 유형을 PlaceholderResolver로 변경할 수도 있습니다. 테스트에서는 다음 예제와 같이 Map 또는 Properties 인스턴스로 지정된 값 집합을 주입하는 데 사용할 수 있습니다.

    HashMap<String, Object> props = new HashMap<>();
    props.put("custom.url", "http://some.url");
    
    Properties properties = new Properties();
    properties.put("myProp", props);
    
    ValueInjectionUtils.injectFieldValues(testTarget, properties);
    

    그러면 dataService의 모든 @Value 주석이있는 필드를 확인하려고 시도합니다. 필자는 개인적으로 ReflectionTestUtils.setField (dataService, "field", "value")에서이 솔루션을 선호합니다. 하드 코드 된 필드 이름에 의존 할 필요가 없기 때문입니다.

  7. from https://stackoverflow.com/questions/9209952/spring-value-injection-in-mockito by cc-by-sa and MIT license