#! /bin/blog

実行形式のブログです。というか、ほぼポエムです。

Powermock + SpringでJUnitをやってみた

Spring Bootはやり方があるらしいが、Spring Boot以前のアプリだったら、どうやるんだ。
ぜんぜん、ぐぐっても出てこねぇので、はまりつつ、やってみた。

まず、アノテーション設定はこうする

  • @RunWithと@PowerMockRunnerDelegateで、JUnit + PowerMock + Spring の組わせる。
  • @PowerMockIgnoreしないとAESのエラー出る。
  • @PrepareForTestにMockにかかわるものを追加しておいた方がよいかも?
@ContextConfiguration(locations = {
        "classpath:PAHT/TO/application-context.xml"})
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PowerMockIgnore ("javax.crypto.*") 
@PrepareForTest({Target.class})
public class MockTest {
...

テスト対象のクラスTargetを@Autowiredつけて、ユニットテストクラスにDIする。
Targetクラスの中で、webServiceTemplateを使用して、外部システムのAPIを呼び出しているので、これをMockと差し替えたい。
だから、@MockをつけてMockしたいクラスとして宣言する。

public class MockTest {

     @Autowired
	private Target target;
	
     @Mock
     WebServiceTemplate webServiceTemplate;

Targetクラスの中で、webServiceTemplateは、実際は、`Response response = webServiceTemplate.marshalSendAndReceive(xxx, xxx)`というように使われている。
このメソッドをMockと差し替えて、意図した戻り値を返すように仕向ける。

	@Test
	public void test() throws Exception {
		
		Response response = new Response();
		response.setResultCode("XXXXX");
		PowerMockito.doReturn(response).when(webServiceTemplate).marshalSendAndReceive(anyString(), any());
		Whitebox.setInternalState(target, WebServiceTemplate.class, webServiceTemplate);

そして、targetを実行する。

		Result res = target.excute();
                Assert.assertEquals(XXX, res);
            }

エラーが出てしまった。
これがイケナイらしい。

		Whitebox.setInternalState(target, WebServiceTemplate.class, webServiceTemplate);

なにをしているかというと、TargetクラスのPrivateフィールド変数webServiceTemplateを、
モック化したwebServiceTemplateで差し替えようとしている。

ところが、targetにPrivateフィールド変数webServiceTemplateが存在しないと言われた。

ん?そんなはずはない。

と思い、デバッガで、targetに、何が格納されているか見たら、Proxy〜みたいな見慣れないものが出た。

それで、思いあたったのが、Spring AOPだ。
そういえば、トランザクション管理やロギング処理などが、target.excuteの前に注入されている。

つまり、実行時は、targetの中身は本来のインスタンスはではないもの、AOPによって注入された処理のインスタンスが格納されている。

困ったあげく、本来のインスタンスを取得できないか調べていたら、ありました。

targetが、AOPによって注入されたインスタンスだったら、そこから、本来のインスタンスをgetできるはず。
しかも、何個もAOPの処理を挟んでいるので、再帰的に剥がしていく必要があった。

	static class AopUtils extends org.springframework.aop.support.AopUtils {
		
		@SuppressWarnings("unchecked")
		public static <T> T unwrapProxy(Object candidate) {
			
			try {
				if (isAopProxy(candidate) && (candidate instanceof Advised)) {
					return (T) unwrapProxy(((Advised) candidate).getTargetSource().getTarget());
				}
			}
			catch (Exception e) {
				throw new IllegalStateException("Failed to unwrap proxied object.", e);
			}

			// else
			return (T) candidate;
		}
	}

そんで、このようにしたら、うまくいきました。

		Whitebox.setInternalState(AopUtils.unwrapProxy(taget), WebServiceTemplate.class, webServiceTemplate);

jmockitのDeencapsulationがdeletedされてた

jmockitには、DeencapsulationというマジカルなUtilityクラスがあります。
通常は、アクセスできないフィールドやメソッドにアクセスすることができちゃう禁断のクラスです。
このクラスが、最新のバージョンで削除されていることが発覚しました。

ことの経緯としては、

1. Java 1.6からJava 1.8にVersion Upしたら
2. jmockitが動かない。内部で、JDKのVersionを判定しているようだ。
3. jmockitをVersion Upしたら
4. DeencapsulationがNoClassDef...

となって、まじか.. ぼくの大すきなDeencapsulationが・・・
とっても便利に使っていたのにどうしよぉおおおパキャマラド パキャマラド
パオパオパオパオ パオパオパオパオと考えていたら、
そうだ。似たものをつくって代用しちゃおうということになりました。

package util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.apache.commons.lang.reflect.FieldUtils;

/**
 * Reflection Utilities.
 * 
 *
 */
public class ReflectionUtils {
	
    /**
     * デフォルトコンストラクタの禁止.
     */
    private ReflectionUtils() {
    }

    /**
     * 対象のスタティックフィールド変数を指定された値で書き換えます.
     * 
     * @param cls 書き換えたいフィールド変数を持つクラス(スタティック変数の変更. static finalもOK. )
     * @param fieldName フィールド変数名
     * @param value 値
     * @throws NoSuchFieldException
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static void writeDeclaredStaticField(@SuppressWarnings("rawtypes") Class cls, String fieldName, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Field field = FieldUtils.getDeclaredField(cls, fieldName, true);
        setFinalStatic(field, value);
    }

    /**
     * 
     * Deencapsulation#setFieldの代用(インスタンス変数の変更。privateもOK.)
     * 
     * 対象インスタンスのフィールド変数を指定された値で書き換えます.
     * 
     * @param cls 書き換えたいフィールド変数を持つクラス
     * @param fieldName フィールド変数名
     * @param value 値
     * @throws IllegalAccessException
     */
    public static void writeField(Object target, String fieldName, Object value) throws IllegalAccessException {
    	FieldUtils.writeField(target, fieldName, value, true);
    }

    private static void setFinalStatic(Field field, Object newValue) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
          field.setAccessible(true);
          Field modifiersField = Field.class.getDeclaredField("modifiers");
          modifiersField.setAccessible(true);
          modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
          field.set(null, newValue);
       }
}


しばらく、javaから離れていたので、トレンドがよくわかりませんが
今、Mockライブラリといえば、PowerMockitoが主流なのでしょうかね。

FTPサーバをiOSアプリに組み込む方法ついて

最近、オイコノミアを録画対象にいれました。
id:ryu-htmです。

iOSアプリをFTPサーバにする方法ついて調べており、
Cで実装されたFTPサーバライブラリをサンプルのアプリに組み込んでました。

github.com


そもそも、アップルは許してくれるのだろうか?
外部からファイルをiOSバイス上に一方的にPUTすることができてしまうわけで、
それはセキュリティを重視するなら、駄目なのではないだろうかという疑念が残ります。