Powermock + SpringでJUnitをやってみた

Spring Bootはやり方があるらしいが、素のSpring Frameworkだったら、どうやるんだろう。
ぜんぜん、ぐぐっても出てこないので、はまりつつ、やってみました。

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

  • @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の中身は本来のインスタンスをラップしたインスタンスが格納されています。

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

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);