簡體   English   中英

mockito 中的 Spring 值注入

[英]Spring value injection in mockito

我正在嘗試為以下方法編寫測試類

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 中。 我想知道是否有辦法完成這項工作。

謝謝

import org.springframework.test.util.ReflectionTestUtils;

@RunWith(MockitoJUnitRunner.class)
public CustomServiceTest{

@InjectMocks
private CustomServiceImpl customService;

@Mock
private DataService dataService;

@Before
public void setup() {
    ReflectionTestUtils.setField(customService, "url", "http://someurl");
}    
...
}

我同意@skaffman 的評論。

除了您的測試使用MockitoJUnitRunner ,因此它不會尋找任何 Spring 的東西,這唯一的目的是初始化 Mockito 模擬。 ContextConfiguration不足以用 spring 連接東西。 從技術上講,如果您想要與彈簧相關的東西,您可以使用 JUnit 的以下運行程序: SpringJUnit4ClassRunner

此外,在編寫單元測試時,您可能需要重新考慮使用 spring。 在單元測試中使用彈簧接線是錯誤的。 但是,如果您要編寫集成測試,那么為什么要在那里使用 Mockito,這沒有意義(如 skaffman 所說)!

編輯:現在在你的代碼中你直接在你的 before 塊中設置CustomerServiceImpl ,這也沒有意義。 那里根本不涉及春天!

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

編輯 2:如果你想編寫CustomerServiceImpl單元測試,那么避免使用 Spring 的東西並直接注入屬性的值。 您也可以使用 Mockito 將DataService模擬直接注入到測試實例中。

@RunWith(MockitoJUnitRunner.class)
public CustomServiceImplTest{
    @InjectMocks private CustomServiceImpl customServiceImpl;
    @Mock private DataService dataService;

    @Before void inject_url() { customServiceImpl.url = "http://..."; }

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

您可能已經注意到,我正在使用對url字段的直接訪問,該字段可以是包可見的。 這是一種實際注入 URL 值的測試解決方法,因為 Mockito 僅注入模擬。

您可以自動連接到一個 mutator(setter),而不僅僅是注釋私有字段。 然后你也可以從你的測試類中使用那個 setter。 無需公開,包私有即可,因為 Spring 仍然可以訪問它,否則只有您的測試可以進入(或同一包中的其他代碼)。

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

我不喜歡僅僅為了測試而以不同的方式自動裝配(與我的代碼庫相比),但是從測試中更改被測類的替代方法簡直是邪惡的。

我有一個從屬性文件中讀取的字符串列表。 @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);
}

你不應該嘲笑你試圖測試的東西。 這是毫無意義的,因為您不會觸及您嘗試測試的任何代碼。 而是從上下文中獲取CustomerServiceImpl的實例。

您可以使用這個小實用程序類 ( gist ) 將字段值自動注入目標類:

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 實用程序類來解析所有占位符值。 您還可以更改參數的類型properties ,以PlaceholderResolver你喜歡用自己的PlaceholderResolver情況。 在您的測試中,您可以使用它來注入一組作為MapProperties實例給出的值,如下例所示:

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"); 因為您不必依賴硬編碼的字段名稱。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM