ITと服と食と

洋服と食をこよなく愛するWebプログラマ。

ユニットテストのナレッジ #1

1ヶ月ぶりに更新します。今年はもっとインプットを増やしてアウトプットも増やしていこうと思います!

まずテスト対象クラスはこちらのShopService.java

@Service
public class ShopService {

	@Autowired
	private ShopRepository shopRepository;

	public Shop getShop(Integer id) {
		return shopRepository.findOne(id);
	}

	public Shop create(ShopCreateRequest request) {
		LocalDateTime now = LocalDateTime.now();

		Shop shop = new Shop();
		shop.setGenre(request.getGenre());
		shop.setShopName(request.getShopName());
		shop.setStation(request.getStation());
		shop.setUrl(request.getUrl());
		shop.setMemo(request.getMemo());
		shop.setCreatedAt(now);

		return shopRepository.saveAndFlush(shop);
	}

	public Shop edit(Integer id, ShopEditRequest request) {
		Shop shop = getShop(id);
		shop.setGenre(request.getGenre());
		shop.setShopName(request.getShopName());
		shop.setStation(request.getStation());
		shop.setUrl(request.getUrl());
		shop.setMemo(request.getMemo());

		return shopRepository.saveAndFlush(shop);
	}
}

続いてテストクラスは、ShopService.java

import com.uchitate.core.entity.Shop;
import com.uchitate.core.repository.ShopRepository;
import com.uchitate.web.support.Genre;
import com.uchitate.web.support.ShopCreateRequest;
import com.uchitate.web.support.ShopEditRequest;
import de.bechte.junit.runners.context.HierarchicalContextRunner;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;

@RunWith(HierarchicalContextRunner.class)
public class ShopServiceTest {

	@InjectMocks
	private ShopService shopService;

	@Mock
	private ShopRepository shopRepository;

	@Captor
	private ArgumentCaptor<Shop> captor;

	@Before
	public void setup() {
		MockitoAnnotations.initMocks(this);
	}

	public class Create {
		private ShopCreateRequest generateShopCreateRequest(
				Genre genre,
				String shopName,
				String station,
				String url,
				String memo) {
			ShopCreateRequest request = new ShopCreateRequest(genre, shopName, station, url, memo);
			return request;
		}

		@Test
		public void success() {
			ShopCreateRequest request = generateShopCreateRequest(
					Genre.JAPANESE,
					"定食屋1",
					"渋谷",
					"http://teisyoku-1.co.jp",
					"トンカツが有名");

			shopService.create(request);
			Mockito.verify(shopRepository).saveAndFlush(captor.capture());
			Shop savedShop = captor.getValue();
			Assertions.assertThat(savedShop)
					.extracting(Shop::getGenre, Shop::getShopName, Shop::getStation, Shop::getUrl, Shop::getMemo)
					.contains(Genre.JAPANESE, "定食屋1", "渋谷", "http://teisyoku-1.co.jp", "トンカツが有名");
		}
	}

	public class Edit {
		private ShopEditRequest generateShopEditRequest(
				Genre genre,
				String shopName,
				String station,
				String url,
				String memo) {
			ShopEditRequest request = new ShopEditRequest(genre, shopName, station, url, memo);
			return request;
		}

		private Shop generateShop(int id, Genre genre, String shopName, String station, String url, String memo) {
			Shop shop = new Shop();
			shop.setId(id);
			shop.setGenre(genre);
			shop.setShopName(shopName);
			shop.setStation(station);
			shop.setUrl(url);
			shop.setMemo(memo);
			return shop;
		}

		@Test
		public void success() {
			ShopEditRequest request = generateShopEditRequest(
					Genre.JAPANESE,
					"定食屋1",
					"渋谷",
					"http://teisyoku-1.co.jp",
					"トンカツが有名");

			Shop shop = generateShop(1, Genre.ITALIAN, "イタリアン1", "代官山", "http://italian-1.co.jp", "ピザ");

			Mockito.when(shopRepository.findOne(Mockito.anyInt())).thenReturn(shop);

			shopService.edit(1, request);
			Mockito.verify(shopRepository).saveAndFlush(captor.capture());
			Shop savedShop = captor.getValue();
			Assertions.assertThat(savedShop)
					.extracting(Shop::getGenre, Shop::getShopName, Shop::getStation, Shop::getUrl, Shop::getMemo)
					.contains(Genre.JAPANESE, "定食屋1", "渋谷", "http://teisyoku-1.co.jp", "トンカツが有名");
		}
	}
}

では、解説。

@RunWith(HierarchicalContextRunner.class)

ランナーは、「HierarchicalContextRunner.class」を指定します。
github.com
このクラスは、テストクラスの階層構造を可能にします。ただそれだけといえばそれだけです。
見た目が美しくなります。私は、テスト対象クラスのメソッド単位でテストクラスを作ってます。


まず、pom.xmlに以下を追加します。

<dependency>
    <groupId>de.bechte.junit</groupId>
    <artifactId>junit-hierarchicalcontextrunner</artifactId>
    <version>4.12.1</version>
</dependency>

あとは、

@RunWith(HierarchicalContextRunner.class)

と書くだけです。

@InjectMocks

このクラスにモックを注入しますよアノテーション

@Mock

モックにするよアノテーション

@Captor

好きなところの状態のオブジェクトを取得したいよアノテーション

@Captor
private ArgumentCaptor<Shop> captor;

Shop.javaのオブジェクトを取得することを指定します。
次をテスト対象クラスのメソッドを呼んだあとに書きます。

Mockito.verify(shopRepository).saveAndFlush(captor.capture());
Shop savedShop = captor.getValue();

これで、「shopRepository.saveAndFlush(shop);」を呼ぶ時に渡す引数のオブジェクトを取得します。

editは、ほぼ同じなので省略します。
以上、ユニットテストのナレッジ #1でした〜