Spring BootでMockitoでモックテスト

Spring BootでMockitoでモックテストを試したメモです。

Spring BootでMockitoを試してみました。

Mockito

Mockitoはjavaのモックライブラリです。
JUnit単体ではモックテストを行うことができないので、
モックライブラリを利用する必要があります。
モックを作成することで、メソッドの呼び出しの検証を行うことができます。

Mockitoでモックオブジェクトを作ってテストしてみようと思います。

使用する準備としてpom.xmlmaven dependencyを追加します。
テストでしか使わないのでtestスコープに入れました。

pom.xml

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

テスト対象

下記のようなPeopleServiceクラスを用意しました。
このクラスは外部サーバのAPIにアクセスして、データを取得するようなサービスクラスを想定してます。

PeopleService.java

package com.example.mockito.service;

import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.net.URISyntaxException;

@Service
public class PeopleService {
    @Autowired
    private RestTemplate restTemplate;

    static private final String HOST = "http://dummy-host.com:8080";

    public People getPeople(String country) {
        String url = HOST + "/api/people/" + country;
        return restTemplate.getForObject(url, People.class);
    }

    public int postPeople(People people) {
        String url = HOST + "/api/people";
        return restTemplate.postForObject(url, people, Integer.class);
    }

    public int putPeople(People people) throws URISyntaxException {
        String url = HOST + "/api/people";

        URI uri = new URI(url);
        RequestEntity<People> requestEntity = RequestEntity
                .put(uri)
                .body(people);

        ResponseEntity<Integer> result = restTemplate.exchange(requestEntity, Integer.class);
        return result.getBody();
    }

    public int deletePeople(String country) {
        String url = HOST + "/api/people/" + country;

        ResponseEntity<Integer> result = restTemplate.exchange(url,
                HttpMethod.DELETE,
                HttpEntity.EMPTY,
                Integer.class,
                country);

        return result.getBody();
    }

    @Data
    @NoArgsConstructor
    @FieldDefaults(level = AccessLevel.PRIVATE)
    public static class People {
        String country;
        int year;
        int population;
    }
}
getPeople()

Peopleオブジェクトを取得。

public People getPeople(String country) {
    String url = HOST + "/api/people/" + country;
    return restTemplate.getForObject(url, People.class);
}
postPeople()

Peopleオブジェクトを登録。

public int postPeople(People people) {
    String url = HOST + "/api/people";
    return restTemplate.postForObject(url, people, Integer.class);
}
putPeople()

Peopleオブジェクトを更新。

public int putPeople(People people) throws URISyntaxException {
    String url = HOST + "/api/people";

    URI uri = new URI(url);
    RequestEntity<People> requestEntity = RequestEntity
            .put(uri)
            .body(people);

    ResponseEntity<Integer> result = restTemplate.exchange(requestEntity, Integer.class);
    return result.getBody();
}
deletePeople()

Peopleオブジェクトを削除。

public int deletePeople(String country) {
    String url = HOST + "/api/people/" + country;

    ResponseEntity<Integer> result = restTemplate.exchange(url,
            HttpMethod.DELETE,
            HttpEntity.EMPTY,
            Integer.class,
            country);

    return result.getBody();
}

Mockitoによるスタブテスト

PeopleServiceで外部サーバのAPIコールをするのですが、
その外部サーバのAPIが出来ていない場合のスタブとして、
またはテスト用のスタブとしてMockitoを使ってスタブテストをしてみます。

(1) @RunWithアノテーションでMockitoJUnitRunner.classを指定することで、モックオブジェクトをインジェクトできます。
(2) @Mockでモックオブジェクトをインジェクトします。
(3) mock()メソッドを使用してモックオブジェクトを生成します。

@SpringBootTest
@RunWith(MockitoJUnitRunner.class) // (1)
public class PeopleServiceTest {
    @Mock // (2)
    PeopleService mockService;

    /*
    private PeopleService mockService;

    @Before
    public void before() {
       mockService = mock(PeopleService.class); // (3)
    }
    */

    :
    :
    :
}
getPeople()メソッドのテスト

(1) モックオブジェクトはそのままではすべてのスタブメソッドでnullを返します。
  そのため、戻り値を定義してやる必要があります。
  戻り値を定義するのは、when()メソッドで対象のスタブメソッド記述し、thenReturn()で戻り値を記述します。
  Peopleオブジェクトを戻すように定義します。
(2) あとは普通にスタブメソッド呼び出しでテストできます。
(3) スタブメソッドが呼び出されたことを検証するには、verify()を使用します。
  times(3)でgetPeople()が3回呼び出されたことを検証しています。
(4) never()でpostPeople()が呼び出されていないことを検証しています。
(5) Mockitoでは前置記法でも記述できます。
  reset()で定義していたスタブメソッドの戻り値をリセットします。
(6) 前置記法ではdoReturn()で期待される値を記述して、その後にwhen()で対象のスタブメソッドを記述します。

    @Test
    public void getPeopleTest() {
        String country = "japan";

        People expected = new People();
        expected.setCountry("japan");
        expected.setYear(2001);
        expected.setPopulation(1_000_000);

        when(mockService.getPeople(country)).thenReturn(expected); // (1)

        // assert
        assertThat(mockService.getPeople(country).getCountry()).isEqualTo("japan"); // (2)
        assertThat(mockService.getPeople(country).getYear()).isEqualTo(2001);
        assertThat(mockService.getPeople(country).getPopulation()).isEqualTo(1_000_000);

        // verify
        verify(mockService, times(3)).getPeople(country); // (3)
        verify(mockService, never()).postPeople(new People()); // (4)

        // 前置記法
        reset(mockService); // (5)
        doReturn(expected).when(mockService).getPeople(country); // (6)
        assertThat(mockService.getPeople(country).getCountry()).isEqualTo("japan");
        assertThat(mockService.getPeople(country).getYear()).isEqualTo(2001);
        assertThat(mockService.getPeople(country).getPopulation()).isEqualTo(1_000_000);
    }
postPeople()のテスト

同様にスタブメソッドを定義してテストします。

    @Test
    public void postPeopleTest() {
        People people = new People();
        people.setCountry("japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);

        when(mockService.postPeople(people)).thenReturn(1);

        assertThat(mockService.postPeople(people)).isEqualTo(1);
    }
putPeople()のテスト

同様にスタブメソッドを定義してテストします。

    @Test
    public void putPeopleTest() throws URISyntaxException {
        People people = new People();
        people.setCountry("japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);

        when(mockService.putPeople(people)).thenReturn(1);

        assertThat(mockService.putPeople(people)).isEqualTo(1);
    }
deletePeople()のテスト

同様にスタブメソッドを定義してテストします。

    @Test
    public void deletePeopleTest() {
        when(mockService.deletePeople("japan")).thenReturn(1);

        assertThat(mockService.deletePeople("japan")).isEqualTo(1);
    }

テストコード

今回のテストコードの全体です。

PeopleServiceTest.java

package com.example.mockito.service;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;

import java.net.URISyntaxException;

import static com.example.mockito.service.PeopleService.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@SpringBootTest
@RunWith(MockitoJUnitRunner.class)
public class PeopleServiceTest {
    @Mock
    PeopleService mockService;

    // @Mock or mock()
    /*
    private PeopleService mockService;

    @Before
    public void before() {
       mockService = mock(PeopleService.class);
    }
    */

    @Test
    public void getPeopleTest() {
        String country = "japan";

        People expected = new People();
        expected.setCountry("japan");
        expected.setYear(2001);
        expected.setPopulation(1_000_000);

        when(mockService.getPeople(country)).thenReturn(expected);

        // assert
        assertThat(mockService.getPeople(country).getCountry()).isEqualTo("japan");
        assertThat(mockService.getPeople(country).getYear()).isEqualTo(2001);
        assertThat(mockService.getPeople(country).getPopulation()).isEqualTo(1_000_000);

        // verify
        verify(mockService, times(3)).getPeople(country);
        verify(mockService, never()).postPeople(new People());

        // 前置記法
        reset(mockService);
        doReturn(expected).when(mockService).getPeople(country);
        assertThat(mockService.getPeople(country).getCountry()).isEqualTo("japan");
        assertThat(mockService.getPeople(country).getYear()).isEqualTo(2001);
        assertThat(mockService.getPeople(country).getPopulation()).isEqualTo(1_000_000);
    }

    @Test
    public void postPeopleTest() {
        People people = new People();
        people.setCountry("japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);

        when(mockService.postPeople(people)).thenReturn(1);

        assertThat(mockService.postPeople(people)).isEqualTo(1);
    }

    @Test
    public void putPeopleTest() throws URISyntaxException {
        People people = new People();
        people.setCountry("japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);

        when(mockService.putPeople(people)).thenReturn(1);

        assertThat(mockService.putPeople(people)).isEqualTo(1);
    }

    @Test
    public void deletePeopleTest() {
        when(mockService.deletePeople("japan")).thenReturn(1);

        assertThat(mockService.deletePeople("japan")).isEqualTo(1);
    }
}

ソースは一応あげときました。

github.com

終わり。