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