理屈はわからないが、How toのメモとして。
次のようなサービスクラスをspringで注入しているリソースクラスのユニットテストをしたい。
となると、サービスのところをモックにして注入したい。HK2で注入してるときのやり方はわかったが、それだとここの実装をかえなきゃいけないので、微妙。試行錯誤して、とりあえずできたやり方を紹介。もっと自然なやり方があればコメントなどお願いします。
HogeResource.java
@Resource
@Path("hoge")
public class HogeResource {
@Autowired
private HogeService service;
/* 以下略 */
下記のようなテストの基底クラスをつくる。
JerseyTestは継承しないでRuleにした方がやりやすい。(というか、そうしないとうまくいかなかった。設定かぶってる感があるが・・・)
TestBase.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestResourceConfig.class, HogeServiceMock.class})
public class AttachementResourceTestBase {
@Autowired
protected HogeService mockService;
@Rule
public JerseyTestResource testResource = new JerseyTestResource(new TestResourceConfig());
}
下記のをそのまんまつかわしていただきました。
http://qiita.com/opengl-8080/items/f90582f6e5cd9db4972b
JerseyTestResource.java
public class JerseyTestResource extends ExternalResource {
private JerseyTest jerseyTest;
public JerseyTestResource(final Application config) {
this.jerseyTest = new JerseyTest() {
@Override
protected Application configure() {
return config;
}
};
}
@Override
public void before() throws Throwable {
this.jerseyTest.setUp();
}
@Override
public void after() {
try {
this.jerseyTest.tearDown();
} catch (Exception e) {
throw new RuntimeException("failed to tear down JerseyTest.", e);
}
}
public JerseyTest getJerseyTest() {
return this.jerseyTest;
}
}
次のクラスで、サービスクラスに注入をモックにしている模様。あとで振る舞いを決めるために呼び出すので、static なインスタンスを注入している。(context.xmlのbeanタグに相当??)
HogeServiceMock.java
@Configuration
public class HogeServiceMock {
public static HogeService mockService = Mockito.mock(HogeService.class);
@Bean
public HogeService hogeService() {
return mockService;
}
}
テストはEnclosedランナーを使って構造化。モックはひとつのインスタンスを使いまわしているので、afterでリセットしないと、呼び出し回数がコントロールできないのに注意。
URIをたたくのは、JerseyTestのtarget()を@Ruleをつけたフィールドから取得して、そいつにさせている。
HogeResourceTest.java
@RunWith(Enclosed.class)
public class HogeResourceTest {
@RunWith(Enclosed.class)
public static class 失敗 {
public static class IDが存在しない extends TestBase {
@Before
public void setUp() throws IOException {
when(mockService.findById(anyInt()).thenReturn(null);
}
@After
public void tearDown() {
reset(mockService);
}
@Test
public void ステータスコードはNOT_FOUND() throws IOException {
WebTarget target = testResource.getJerseyTest().target();
int status = target.path("hoge/123").request().get().getStatus();
verify(mockService, times(1)).findById(123);
assertThat(status, is(NOT_FOUND.getStatusCode()));
}
}