web-dev-qa-db-ja.com

mockitoでのSpring値の注入

次のメソッドのテストクラスを記述しようとしています

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

クラスのメソッドの1つで注入された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に読み込まれません。とにかくこれを成し遂げる方法があるのか​​と思っていました。

ありがとう

21
rohit

プライベートフィールドに注釈を付けるだけでなく、ミューテーター(セッター)に自動配線できます。その後、テストクラスからそのセッターを使用することもできます。それを公開する必要はありません。Springが引き続きアクセスできるので、パッケージを非公開にすることができます。

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

私はテストのためだけに(私のコードベースと比較して)異なるオートワイヤリングのファンではありません。

3
Joseph Lust
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");
}    
...
}
50
Robert Hutto

@skaffmanのコメントに同意します。

テストではMockitoJUnitRunnerを使用しているため、Springのものは検索されません。この目的はMockitoモックを初期化することだけです。 ContextConfigurationは、物を春に配線するには十分ではありません。技術的には、JUnitを使用して、スプリング関連のものが必要な場合は次のランナーを使用できます:SpringJUnit4ClassRunner

また、nit Testを記述しているときに、Springの使用を再検討することをお勧めします。単体テストでのスプリング配線の使用は間違っています。ただし、代わりにIntegration Testを記述しているのに、なぜ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 customService;
    @Mock private DataService dataService;

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

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

urlフィールドへの直接アクセスを使用していることに気付いたかもしれませんが、フィールドはパッケージから見えるようにすることができます。 Mockitoはモックのみを挿入するため、これは実際にURL値を挿入するためのテスト回避策です。

12
Brice

プロパティファイルから読み取る文字列のリストがありました。 @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);
}
1
lakshmi

テストしようとしているものをあざけってはいけません。テストしようとしているコードには触れないので、それは無意味です。代わりに、コンテキストからCustomerServiceImplのインスタンスを取得します。

1
John B

この小さなユーティリティクラス( 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ユーティリティクラスを使用してすべてのプレースホルダー値を解決します。独自のPlaceholderResolverを使用する場合は、パラメータのタイプpropertiesPlaceholderResolverに変更することもできます。テストでは、これを使用して、次の例のように、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");よりもこのソリューションを個人的に好みます。

0
Jan Gassen