- 1. Spring FrameworkのUnitテスト実装方法 3-1.Controllerテスト(Junit4, spring-test, MockMvc(StandaloneSetupMode), MockClass)
- 2. 3-1.Controllerテスト Junit4, spring-testライブラリ、MockClassによるモック化を使用したパターン
Spring FrameworkのUnitテスト実装方法 3-1.Controllerテスト(Junit4, spring-test, MockMvc(StandaloneSetupMode), MockClass)
TERASOLUNA Server Framework for Java (5.x) Development Guideline
サンプルソースはこちら
(これのMyBatis3を使用したパターンで作成してます。)
3-1.Controllerテスト Junit4, spring-testライブラリ、MockClassによるモック化を使用したパターン
controllerクラスのテストです。
Serviceクラスのメソッドをモッククラスを用いてモック化しています。
モック化については2-2のServiceテストでやったことと変わらないのですが、Springにおけるcontrollerクラスのテストの特徴として、MockMVCというspring-testライブラリの中の機能を使う点にあります。MockMVCはspring frameworkで作られたwebアプリケーションのDIコンテナの機能の部分を疑似的に再現してくれるspring-testの機能です。
viewである画面で入力情報を入力し、ボタンを押下した後にcontroller層に遷移する流れを再現してくれます。
簡単に言うとテスト対象のcontrollerクラスへ疑似的なリクエストを投げてくれますよ。ということです。
さらに、MockMVCには2パターンの起動方法があります。
standaloneSetupパターン
・MockMVCの機能を使いつつ、必要最小限の構成でテストができる。
・controllerクラスを引数として起動するので、mockitoライブラリの使用が可能
webAppContextSetupパターン
・デプロイ環境とほぼ同様の条件でのテストができる。(DIコンテナの設定とかxmlの設定全部する。)
・クラス単位の起動というわけではないので、mockitoライブラリの使用が不可。
・モック化する場合はテスト用のcontext.xmlを作成してxml上でモック化する必要がある。
今回はstandaloneSetupパターンでの実装をしていきます。
【pom.xml】
Junit、spring-testライブラリが必要です。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
【テスト対象クラス】
TodoController.java
テスト対象はfinishメソッド
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "finish", method = RequestMethod.POST)
public String finish(@Validated({Default.class}) TodoForm form, BindingResult bindingResult, Model model, RedirectAttributes attributes) {
if(bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.finish(form.getTodoId());
}catch (BusinessException e) {
// TODO: handle exception
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
}
(対象メソッドの処理の部分だけ抜粋してます。)
【テストクラス】
TodoControllerTestVerStandaloneMockClass.java
public class TodoControllerTestVerStandaloneMockClass {
TodoController target;
MockMvc mockMvc;
@Before
public void setUp() {
//モッククラスをテストメソッドによって切り替えるため処理なし
}
//正常動作のパターン
@Test
public void testFinish() throws Exception {
//Serviceのモック化
target = new TodoController();
TodoService mockService = new TestFinishMock();
target.todoService = mockService;
//standaloneモードでmockMvcを起動
mockMvc = MockMvcBuilders.standaloneSetup(target).build();
//Controllerに投げるリクエストを作成
MockHttpServletRequestBuilder getRequest =
MockMvcRequestBuilders.post("/todo/finish")
.param("todoId", "cceae402-c5b1-440f-bae2-7bee19dc17fb")
.param("todoTitle", "one");
//Controllerにリクエストを投げる(リクエストからfinishメソッドを起動させる。)
ResultActions results = mockMvc.perform(getRequest);
//結果検証
results.andDo(print());
results.andExpect(status().isFound());
results.andExpect(view().name("redirect:/todo/list"));
//FlashMapのデータを取得(model.addFlashAttributeに設定したmessageオブジェクトのtextを取得する。半ば強引)
FlashMap flashMap = results.andReturn().getFlashMap();
Collection<Object> collection = flashMap.values();
for(Object obj : collection) {
ResultMessages messages = (ResultMessages) obj;
ResultMessage message = messages.getList().get(0);
String text = message.getText();
assertEquals("Finished successfully!", text);
}
}
//serviceメソッドが異常を返した場合
@Test
public void testFinishThrowException() throws Exception {
//Serviceのモック化
target = new TodoController();
TodoService mockService = new TestFinishExceptionMock();
target.todoService = mockService;
///standaloneモードでmockMvcを起動
mockMvc = MockMvcBuilders.standaloneSetup(target).build();
//Controllerに投げるリクエストを作成
MockHttpServletRequestBuilder getRequest =
MockMvcRequestBuilders.post("/todo/finish")
.param("todoId", "cceae402-c5b1-440f-bae2-7bee19dc17fb")
.param("todoTitle", "one");
//Controllerにリクエストを投げる(リクエストからfinishメソッドを起動させる。)
ResultActions results = mockMvc.perform(getRequest);
//結果検証
results.andDo(print());
results.andExpect(status().isOk());
results.andExpect(view().name("todo/list"));
//model Confirmation
ModelAndView mav = results.andReturn().getModelAndView();
//結果検証
ResultMessages actResultMessages = (ResultMessages)mav.getModel().get("resultMessages");
ResultMessage actResultMessage = actResultMessages.getList().get(0);
String text = actResultMessage.getText();
assertEquals("[E004]The requested Todo is not found. (id=cceae402-c5b1-440f-bae2-7bee19dc17fb)", text);
}
}
今回テスト対象として選んだメソッドがリダイレクトを返すパターンなので少し注意です。
なので、結果検証で期待する値もisOK()ではなくisFound()としてます。200じゃなくて302を期待してます。
あと、その後の検証ではattributes.addFlashAttributeでリダイレクト先に受け渡すデータの中身を検証しているのですが、
addされているResultMessagesというのがサンプルとして使用したterasolunaの機能になるので、ここのデータの中身の確認の方法はあまり参考にならないかもしれません。
テストケースは正常系と異常系で2パターン用意してみました。
正常系の方はリダイレクト遷移しFlashAttributeでデータを受け渡しているのでそのデータを持ってきており、
異常系の方はViewにそのまま遷移しModelAttributeでデータをviewに渡しているのでそのデータを取得してます。
【その他設定】
TestFinishMock.java
正常系用のモッククラスです。
(モック対象メソッドの処理の部分だけ抜粋してます。)
public class TestFinishMock implements TodoService{
//mockを作成
@Override
public Todo finish(String todoId) {
// TODO 自動生成されたメソッド・スタブ
Todo todo = new Todo();
try {
//getTodoTrueDataメソッドでテスト用データを作成
todo = getTodoTrueData();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return todo;
}
}
TestFinishExceptionMock.java
異常系用のモッククラスです。
(モック対象メソッドの処理の部分だけ抜粋してます。)
public class TestFinishExceptionMock implements TodoService{
//mockを作成(exceptionを返す)
@Override
public Todo finish(String todoId) {
// TODO 自動生成されたメソッド・スタブ
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage.fromText("[E004]The requested Todo is not found. (id=cceae402-c5b1-440f-bae2-7bee19dc17fb)"));
throw new BusinessException(messages);
}
}
単純にメソッド単位という意味でのUnit testであれば、standaloneSetupパターンで行うのが望ましいのではないかと思います。
しかもmockitoとか使えてテスト実装が楽だし。(xmlでモック化とかめんどくさいし・・・)
サンプルソースはgithubで公開してます。
Spring Unit Test pattern11