Spring FrameworkのUnitテスト実装方法 2-3.Serviceテスト(Junit4, mockito)

Spring FrameworkのUnitテスト実装方法 2-3.Serviceテスト(Junit4, mockito)

【サンプルソース】
TERASOLUNA Server Framework for Java (5.x) Development Guideline
サンプルソースはこちら
(これのMyBatis3を使用したパターンで作成してます。)

2-3.Serviceテスト Junit4、mockitoライブラリを使用したパターン

Serviceクラスのテストです。
Repositoryクラスのメソッドをモック化してテストしています。
mockitoライブラリを使って、Repositoryクラスのメソッドが呼ばれた際にモック用の戻り値を返すという流れです。

【pom.xml】

Junit、mockitoライブラリが必要です。

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

【テスト対象クラス】

TodoServiceImpl.java
テスト対象はfinishメソッド

@Service
@Transactional
public class TodoServiceImpl implements TodoService {

    private static final long MAX_UNFINISHED_COUNT = 5;

    @Inject
    TodoRepository todoRepository;

    public Todo findOne(String todoId) {
        Todo todo = todoRepository.findOne(todoId);
        if (todo == null) {
            ResultMessages messages = ResultMessages.error();
            messages.add(ResultMessage.fromText("[E004]The requested Todo is not found. (id=" + todoId + ")"));

            throw new ResourceNotFoundException(messages);
        }
        return todo;
    }

    @Override
    public Todo finish(String todoId) {
        // TODO 自動生成されたメソッド・スタブ
        Todo todo = findOne(todoId);
        if (todo.isFinished()) {
            ResultMessages messages = ResultMessages.error();
            messages.add(ResultMessage.fromText("[E002]The requested Todo is already finished. (id=" + todoId + ")"));

            throw new BusinessException(messages);
        }
        todo.setFinished(true);
        todoRepository.update(todo);
        return todo;
    }
}

モック化の対象メソッドは、todoRepository.updateメソッドとtodoRepository.findOneメソッドになります。

【テストクラス】

TodoServiceImplTestVerMockito.java

public class TodoServiceImplTestVerMockito {

    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();
    
    @InjectMocks
    TodoServiceImpl target;
    
    @Mock
    TodoRepository todoRepository;
    
    @Before
    public void setUp() throws Exception{
        //モッククラスの適用(updateメソッドのモック化)
        //trueを返す
        when(todoRepository.update(anyObject())).thenReturn(true);
    }
    
    //正常に動作したパターン
    @Test
    public void testFinishOK() throws Exception{
        
        //モッククラスの適用(findOneメソッドのモック化)
        //todoのfinishedにfalseが格納されているデータを返す
        when(todoRepository.findOne(anyString())).thenReturn(getTodoFalseData());
        
        //引数設定
        String todoId = "cceae402-c5b1-440f-bae2-7bee19dc17fb";
        
        //finishメソッドのテスト
        Todo todo = target.finish(todoId);
        
        //結果検証(assertTodoメソッドはメソッドの実行によって返ってきたTodoオブジェクトを検証するメソッド)
        assertTodo(todo);
        
    }
    
    //取得したTodoオブジェクトのfinishedが既にtrueで異常が発生したパターン
    //@Testのexpectedには期待するExceptionクラスを設定する。こうすることでExceptionが発生することは想定通りということを宣言している。
    @Test(expected = BusinessException.class)
    public void testFinishNG() throws Exception{
        
        //モッククラスの適用(findOneメソッドのモック化)
        //todoのfinishedにtrueが格納されているデータを返す
        when(todoRepository.findOne(anyString())).thenReturn(getTodoTrueData());
        
        //引数設定
        String todoId = "cceae402-c5b1-440f-bae2-7bee19dc17fb";
        
        //try-catch文はなくてもJunitとしては正常になるが、printStackTraceメソッドでエラーの内容を表示させている。
        try {
            target.finish(todoId);
        }catch (BusinessException e) {
            // TODO: handle exception
            e.printStackTrace();
            
            throw e;
        }
    }
    
    //モック用の戻り値データを作成(testFinishOK()用)
    public Todo getTodoFalseData() throws Exception {
        
        Todo todo = new Todo();
        todo.setTodoId("cceae402-c5b1-440f-bae2-7bee19dc17fb");
        todo.setTodoTitle("one");
        todo.setFinished(false);
        SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String strDate = "2017-10-01 15:39:17.888";
        Date date1 = sdFormat.parse(strDate);
        todo.setCreatedAt(date1);
        
        return todo;
    }
    
    //モック用の戻り値データを作成(testFinishNG()用)
    public Todo getTodoTrueData() throws Exception {
        
        Todo todo = new Todo();
        todo.setTodoId("cceae402-c5b1-440f-bae2-7bee19dc17fb");
        todo.setTodoTitle("one");
        todo.setFinished(true);
        SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String strDate = "2017-10-01 15:39:17.888";
        Date date1 = sdFormat.parse(strDate);
        todo.setCreatedAt(date1);
        
        return todo;
    }
}

想定通りにデータを取ってきた時のパターンと、
異常が発生した時のパターンの2パターンを記述しました。
期待通りの異常が発生した場合は @Test(expected = Exceptionクラス)と設定することで、Junitのテストが成功になります。

【その他設定】

特になし。

モッククラスを用いたモック化と比べると、モック用のファイルを作成する必要が無い分モッククラスの数の管理がなくなるのでmockitoの方が楽そうです。
ちなみに、mockitoではprivateメソッドや、staticメソッドなどのモック化はできません。
必要ならPowerMock等のライブラリを使います。

サンプルソースはgithubで公開してます。
Spring Unit Test pattern10

Junitカテゴリの最新記事