#! /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);