Spring BootのControllerクラス、ServiceクラスRepositoryクラスをJUnitとDBUnitを用いてテストするサンプルコードです。
環境
- OS:Windows10
- IDE:Eclipse2020-03
- Java:8
- MySQL:5.7
- Spring Boot:2.3.0
- JUnit:5
- DBUnit:2.7.0
階層構造
spring_boot_test_item
|
src/main/java
|----jp.co.test_item
| |----app
| | |---- controller
| | |---- response
| |
| |----domain
| |---- service
| |---- repository
| |---- model
|
src/main/resources
|----application.yml
|
|
src/main/test(※)
※src/main/test配下は、基本的に、src/main/java配下と同構成になるため割愛
テーブルの作成
CREATE TABLE `items` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
`item_explanation` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`category_id` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
上記DDLを実行して、テーブルを作成します。
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://接続先名/DB名?serverTimezone=JST&nullCatalogMeansCurrent=true
username: ユーザー名
password: パスワード
jpa:
database: MYSQL
hibernate.ddl-auto: update
main:
allow-bean-definition-overriding: true
MySQLへの接続情報を記載します。
ポイントは以下2つ。
- 「nullCatalogMeansCurrent=true」とすることで、同DBインスタンスの別データベースに同名のテーブルが存在する場合のエラーを回避
- 「allow-bean-definition-overriding: true」(上の「main:」も忘れずに)とすることで、beanオーバーライドを許可
してあげましょう。
build.gradleの設定
plugins {
id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'jp.co.test_item'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencyManagement {
imports {
mavenBom "org.junit:junit-bom:5.6.2"//BOMのインポート
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.4'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.junit.jupiter:junit-jupiter'//JUnit5に対応
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'//IDEなどのサポート用
testCompile group: 'com.github.springtestdbunit', name: 'spring-test-dbunit', version: '1.3.0' // SpringでDBUnitを用いる際に必要
testCompile group: 'org.dbunit', name: 'dbunit', version: '2.7.0' // DBUnitのAPI
}
test {
useJUnitPlatform()
}
コメントの箇所が今回のポイントです。
テスト対象の実装
テスト対象となるクラス群を実装します。
1. Controllerクラス
package jp.co.spring_boot_test_item.app.controller;
import java.sql.SQLException;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jp.co.spring_boot_test_item.app.request.ItemPostRequest;
import jp.co.spring_boot_test_item.app.response.ItemResponse;
import jp.co.spring_boot_test_item.app.response.ItemsResponse;
import jp.co.spring_boot_test_item.domain.model.Item;
import jp.co.spring_boot_test_item.domain.service.ItemService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/item/{itemId}")
public ResponseEntity<ItemResponse> getItemsByGet(@PathVariable int itemId){
Item item = itemService.findByItemId(itemId);
ItemResponse itemResponse = ItemResponse.builder().item(item).build();
return new ResponseEntity<>(itemResponse, HttpStatus.OK);
}
@PostMapping("/items")
public ResponseEntity<ItemsResponse> getItemsByPost(@Validated @RequestBody ItemPostRequest request){
List<Item> items = itemService.findByCategoryId(request.getCategoryId());
ItemsResponse itemResponse = ItemsResponse.builder().items(items).build();
return new ResponseEntity<>(itemResponse, HttpStatus.OK);
}
}
@GetMapping、@PostMappingをつけた2つのメソッドが今回のテスト対象となります。
2. Requestクラス
package jp.co.spring_boot_test_item.app.request;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemPostRequest {
private Integer categoryId;
}
上のControllerクラスの@PostMappingの付いたメソッドのRequestBodyの内容取得に使用します。
3. Responseクラス
package jp.co.spring_boot_test_item.app.response;
import java.util.List;
import jp.co.spring_boot_test_item.domain.model.Item;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class ItemResponse {
// Item返却用
private Item item;
// List<Item>返却用
private List<Item> items;
}
Controllerクラスでのレスポンス時に形を整えるために使用します。
4. Serviceクラス
package jp.co.spring_boot_test_item.domain.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jp.co.spring_boot_test_item.domain.model.Item;
import jp.co.spring_boot_test_item.domain.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
@Transactional(readOnly = true)
public Item findByItemId(int itemId) {
return itemRepository.findByItemId(itemId);
}
@Transactional(readOnly = true)
public List<Item> findByCategoryId(int categoryId) {
return itemRepository.findByCategoryId(categoryId);
}
}
Serviceクラスです。
データの読み取り操作のみのため、@Transactional(readOnly=true)としておきます(デフォルトはfalse)。
こうすることで、データ操作処理(登録、更新、削除)処理をしようとしても処理は行われず、エラーも発生しなくなります。
(そもそもそんな記述をしていませんが、、)
5. ItemRepository.java
package jp.co.spring_boot_test_item.domain.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import jp.co.test_item.domain.model.Item;
@Repository
public interface ItemRepository extends JpaRepository<Item, Integer>{
// 「select * from items where item_id = itemId(引数としてわたってくる変数);」と同義
public Item findByItemId(int itemId);
// 「select * from items where category_id = categoryId(引数としてわたってくる変数);」と同義
public List<Item> findByCategoryId(int categoryId);
}
Repositoryクラスです。
Spring Data JPAの命名ルールに従ってメソッドを定義することで、クエリを自動生成してくれます。
6. Entityクラス
package jp.co.spring_boot_test_item.domain.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* The persistent class for the items database table.
*
*/
@Entity
@Table(name="items")
@NamedQuery(name="Item.findAll", query="SELECT i FROM Item i")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="item_id")
private int itemId;
@Column(name="category_id")
private int categoryId;
@Column(name="item_explanation")
private String itemExplanation;
@Column(name="item_name")
private String itemName;
public Item() {
}
public int getItemId() {
return this.itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCategoryId() {
return this.categoryId;
}
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
}
public String getItemExplanation() {
return this.itemExplanation;
}
public void setItemExplanation(String itemExplanation) {
this.itemExplanation = itemExplanation;
}
public String getItemName() {
return this.itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
}
Entityクラスです。
今回は、DBのテーブル定義をもとにHibernate ORMを用いて生成しました。
テストクラスの実装
1. ItemControllerTest.java
package jp.co.spring_boot_test_item.app.controller;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RestController;
import jp.co.spring_boot_test_item.domain.model.Item;
import jp.co.spring_boot_test_item.domain.service.ItemService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class ItemControllerTest {
MockMvc mockMvc;
@Mock // モックオブジェクトとして使用することを宣言
private ItemService itemService;
@InjectMocks // モックオブジェクトの注入
private ItemController itemController;
@BeforeEach // テストメソッド(@Testをつけたメソッド)実行前に都度実施
public void initmocks() {
MockitoAnnotations.initMocks(this); // アノテーションの有効化
mockMvc = MockMvcBuilders.standaloneSetup(itemController).build(); // MockMvcのセットアップ
}
@Test
public void test_getItemsByGet() throws Exception {
when(itemService.findByItemId(1)).thenReturn(getItemIdOfItemId1()); // itemService.findByItemId(1)実行時にgetItemIdOfItemId1()の結果が返ることを定義
this.mockMvc.perform(get("/item/{itemId}", 1)) // @GetMapping("/item/{itemId}")のメソッドの実行と結果確認
.andExpect(status().isOk()) // 以下、結果確認
.andExpect(jsonPath("$.item.itemId").value(1))
.andExpect(jsonPath("$.item.itemName").value("ふしぎなアメ"))
.andExpect(jsonPath("$.item.itemExplanation").value("レベル1アップします。"))
.andExpect(jsonPath("$.item.categoryId").value(1));
}
@Test
public void test_getItemsByPost() throws Exception {
when(itemService.findByCategoryId(1)).thenReturn(getItemIdOfCategory1()); // findByCategoryId(1)実行時にgetItemIdOfCategory1()の結果が返ることを定義
this.mockMvc.perform(post("/items").contentType(MediaType.APPLICATION_JSON).content("{\"categoryId\":\"1\"}")) // RequestBodyの要素の型と値を設定
.andExpect(jsonPath("$.items", hasSize(2))) // 以下、結果確認
.andExpect(status().isOk())
.andExpect(jsonPath("$.items[0].itemId").value(1))
.andExpect(jsonPath("$.items[0].itemName").value("ふしぎなアメ"))
.andExpect(jsonPath("$.items[0].itemExplanation").value("レベル1アップします。"))
.andExpect(jsonPath("$.items[0].categoryId").value(1))
.andExpect(jsonPath("$.items[1].itemId").value(2))
.andExpect(jsonPath("$.items[1].itemName").value("オボンのみ"))
.andExpect(jsonPath("$.items[1].itemExplanation").value("HPをすこしだけかいふくする。"))
.andExpect(jsonPath("$.items[1].categoryId").value(1));
}
public Item getItemIdOfItemId1() {
Item item = new Item();
item.setItemId(1);
item.setItemName("ふしぎなアメ");
item.setItemExplanation("レベル1アップします。");
item.setCategoryId(1);
return item;
}
public List<Item> getItemIdOfCategory1() {
List<Item> items = new ArrayList<>();
Item item1 = new Item();
item1.setItemId(1);
item1.setItemName("ふしぎなアメ");
item1.setItemExplanation("レベル1アップします。");
item1.setCategoryId(1);
Item item2 = new Item();
item2.setItemId(2);
item2.setItemName("オボンのみ");
item2.setItemExplanation("HPをすこしだけかいふくする。");
item2.setCategoryId(1);
items.add(item1);
items.add(item2);
return items;
}
}
Controllerクラスのテストです。
@RestControllerなので、JSON形式でやりとりされるのがポイント。
テストコード自体も、合わせた内容にします。
2. ItemServiceTest.java
package jp.co.spring_boot_test_item.domain.service;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import jp.co.spring_boot_test_item.domain.model.Item;
import jp.co.spring_boot_test_item.domain.repository.ItemRepository;
public class ItemServiceTest {
@Mock // モックオブジェクトとして使用することを宣言
private ItemRepository itemRepository;
@InjectMocks // モックオブジェクトの注入
private ItemService itemService;
@BeforeEach // テストメソッド(@Testをつけたメソッド)実行前に都度実施
public void initmocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test_findByItemId() {
when(itemRepository.findByItemId(100)).thenReturn(getItemId100()); // itemRepository.findByItemId(100)実行時にgetItemId100()の結果が返ることを定義
Item item = itemRepository.findByItemId(100);
// 以下、結果確認
assertThat(item.getItemId(), is(100));
assertThat(item.getItemName(), is("超ふしぎなアメ"));
assertThat(item.getItemExplanation(), is("レベル100アップします。"));
assertThat(item.getCategoryId(), is(1));
}
@Test
public void test_findByCategoryId() {
when(itemRepository.findByCategoryId(10)).thenReturn(getItemIdOfCategory10()); // itemRepository.findByCategoryId(10)実行時にgetItemIdOfCategory10()の結果が返ることを定義
List<Item> items = itemRepository.findByCategoryId(10);
// 以下、結果確認
assertThat(items.get(0).getItemId(), is(1));
assertThat(items.get(0).getItemName(), is("ふしぎなアメ"));
assertThat(items.get(0).getItemExplanation(), is("レベル1アップします。"));
assertThat(items.get(0).getCategoryId(), is(1));
assertThat(items.get(1).getItemId(), is(2));
assertThat(items.get(1).getItemName(), is("オボンのみ"));
assertThat(items.get(1).getItemExplanation(), is("HPをすこしだけかいふくする。"));
assertThat(items.get(1).getCategoryId(), is(1));
}
public Item getItemId100() {
Item item = new Item();
item.setItemId(100);
item.setItemName("超ふしぎなアメ");
item.setItemExplanation("レベル100アップします。");
item.setCategoryId(1);
return item;
}
public List<Item> getItemIdOfCategory10(){
List<Item> items = new ArrayList<>();
Item item1 = new Item();
item1.setItemId(1);
item1.setItemName("ふしぎなアメ");
item1.setItemExplanation("レベル1アップします。");
item1.setCategoryId(1);
Item item2 = new Item();
item2.setItemId(2);
item2.setItemName("オボンのみ");
item2.setItemExplanation("HPをすこしだけかいふくする。");
item2.setCategoryId(1);
items.add(item1);
items.add(item2);
return items;
}
}
Controllerクラスのテストでも用いていますが、Mockitoを使用して下階層のRepositoryクラスの返却値を定義するのがポイント。
3. ItemRepositoryTest.java
package jp.co.spring_boot_test_item.domain.repository;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import jp.co.spring_boot_test_item.domain.model.Item;
@SpringBootTest
@Transactional
public class ItemRepositoryTest {
@Autowired
JdbcTemplate jdbctemplate;
@Autowired
ItemRepository itemRepository;
String item_id_str = null;
Integer item_id_num = null;
private File file = null;
private FileOutputStream out;
private FileInputStream in;
File tempDir = new File("src/test/java/jp/co/spring_boot_test_item/domain/repository");
@BeforeTransaction // 各@Transactional実行前に都度実施
public void initdb() throws Exception{
Connection connection = jdbctemplate.getDataSource().getConnection();
IDatabaseConnection dbconnection = new DatabaseConnection(connection);
try {
// AUTO_INCREMENTの現在値を取得
String sql = "SHOW TABLE STATUS where name = 'items'";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet result = statement.executeQuery();
while (result.next()) {
item_id_str = result.getString("Auto_increment");
}
// itemsテーブルのバックアップを取得
QueryDataSet partialDataSet = new QueryDataSet(dbconnection);
partialDataSet.addTable("items");
file=File.createTempFile("items",".xml", tempDir);
out = new FileOutputStream(file);
FlatXmlDataSet.write(partialDataSet, out);
out.flush();
out.close();
// DBの値を削除し、テストデータを投入する(ExcelファイルのパスはItemRepositoryTest.javaと同階層)
IDataSet dataset = new XlsDataSet(new File("src/test/java/jp/co/spring_boot_test_item/domain/repository/ItemRepositoryTest.xlsx"));
DatabaseOperation.CLEAN_INSERT.execute(dbconnection, dataset);
}catch(Exception e) {
e.printStackTrace();
}finally {
connection.close();
dbconnection.close();
}
}
@AfterTransaction // 各@Transactional実行後に都度実施
public void teardown() throws Exception{
Connection connection = jdbctemplate.getDataSource().getConnection();
IDatabaseConnection dbconnection = new DatabaseConnection(connection);
try {
// テストデータを削除し、バックアップファイルの内容を戻す
in = new FileInputStream(file);
IDataSet dataSet= new FlatXmlDataSetBuilder().build(in);
DatabaseOperation.CLEAN_INSERT.execute(dbconnection,dataSet);
// AUTO_INCREMENTをテスト実行前の値に戻す
String sql = new StringBuilder("ALTER TABLE items AUTO_INCREMENT=").append(item_id_str).toString();
PreparedStatement statement = connection.prepareStatement(sql);
statement.executeUpdate();
}catch(Exception e) {
e.printStackTrace();
}finally {
connection.close();
dbconnection.close();
file.deleteOnExit(); // DBバックアップファイルの削除
}
}
@Test
public void test_findByItemId() {
Item expectedItem = new Item();
Item actualItem = itemRepository.findByItemId(1);
assertThat(actualItem.getItemId(), is(1));
assertThat(actualItem.getItemName(), is("ふしぎなアメ"));
assertThat(actualItem.getItemExplanation(), is("レベル1アップします。"));
assertThat(actualItem.getCategoryId(), is(1));
}
@Test
public void test_findByCategoryId() {
Item item1 = new Item();
item1.setItemId(1);
item1.setItemName("ふしぎなアメ");
;
item1.setItemExplanation("レベル1アップします。");
item1.setCategoryId(1);
Item item2 = new Item();
item2.setItemId(4);
item2.setItemName("オボンのみ");
item2.setItemExplanation("HPをすこしだけかいふくする。");
item2.setCategoryId(1);
List<Item> expectedItems = new ArrayList<Item>();
expectedItems.add(item1);
expectedItems.add(item2);
List<Item> actualItems = itemRepository.findByCategoryId(1);
assertThat(actualItems, hasSize(2));
for (int i = 0; i < actualItems.size(); i++) {
for (int j = 0; j < expectedItems.size(); j++) {
if (actualItems.get(i).getItemId() == expectedItems.get(j).getItemId()) {
assertThat(actualItems.get(i).getItemName(), is(expectedItems.get(j).getItemName()));
assertThat(actualItems.get(i).getItemExplanation(), is(expectedItems.get(j).getItemExplanation()));
assertThat(actualItems.get(i).getCategoryId(), is(expectedItems.get(j).getCategoryId()));
break;
}
}
}
}
@Test
public void test_save() throws Exception{
// 更新
Item Item1 = new Item();
Item1.setItemId(1);
Item1.setItemName("ふつうのアメ");
Item1.setItemExplanation("20kcal摂取できます。");
Item1.setCategoryId(3);
itemRepository.save(Item1);
// 追加
Item item2 = new Item();
item2.setItemName("ペロペロキャンディー");
item2.setItemExplanation("子どもにあげると喜ばれます(たぶん)。");
item2.setCategoryId(3);
itemRepository.save(item2);
// DBの値の確認(更新分)
Item dbItem1 = itemRepository.findByItemId(1);
assertThat(dbItem1.getItemId(), is(1));
assertThat(dbItem1.getItemName(), is("ふつうのアメ"));
assertThat(dbItem1.getItemExplanation(), is("20kcal摂取できます。"));
assertThat(dbItem1.getCategoryId(), is(3));
// DBの値の確認(追加分)
item_id_num = Integer.valueOf(item_id_str);
Item dbItem2 = itemRepository.findByItemId(item_id_num);
assertThat(dbItem2.getItemId(), is(item_id_num));
assertThat(dbItem2.getItemName(), is("ペロペロキャンディー"));
assertThat(dbItem2.getItemExplanation(), is("子どもにあげると喜ばれます(たぶん)。"));
assertThat(dbItem2.getCategoryId(), is(3));
}
}
Repositoryクラスのテストです。
ポイントは以下の2つ。
-
@Mockを用いる代わりに、クラスの上に@SpringBootTestアノテーションを付与
DBUnitの機能により、テストメソッド実行前後に、テストデータとDBデータの入れ替えが行われます。 - @Transactionalを用いてのテストなので、テスト実行前後に、@BeforeEach/@AfterEachでなく、@BeforeTransaction/@AfterTransactionを用いる(そうしないと特にCUD系の処理の時に、トランザクションロックがかかる可能性があります)
今回、上の階層では使っていませんが、挿入、更新(insert, update)系のコードも書き、テスト後にDBのAUTO_INCREMENTを実行前のものに戻す記述も入れてみました。
ItemRepositoryTest.xlsx
Repositoryクラスのテスト用のデータです。
今回は、ItemRepositoryTest.javaと同階層に配置して使います。
ポイントは、
- 1行目にカラム名を記載する
- シート名はテーブル名にする(今回は「items」)
ことです。
補足事項
- Controllerクラスには、画面遷移を伴う@Controllerではなく、WebAPIで用いられる、@RestControllerを用いています。
- 今回は各クラスの書き方に重点を置いているので、例外やエラーのテストは省略しています。
本記事の更新履歴
- 2020年6月13日:
コードをGitHub
spring-boot-test-sample
に追加。 - 2020年6月15日:Entityクラス、Serviceクラスのトランザクション制御に関する記述を追記
参考
■JUnit
JUnit全般
appication.ymlの設定
build.gradleの設定
- Testing in Java & JVM projects | Gradle6.4.1
- Gradleプロジェクトでspring-boot-starter-testにJUnit5を導入するときの競合を解決する | Qiita
アノテーション
- JUnit 4からJUnit 5に移行する | Qiita
- Eclipse を使って JUnit5 を導入したときのメモ | Qiita
- テスト | Spring
- springbootのテストについて-備忘録- | 忘れっぽいエンジニアの備忘録
アサーション
- JUnit4とJUnit5のアサーション | DMCA
-
Class Assertions | junit.org
→ JUnit5 -
Class Assert | junit.org
→ JUnit4
テスト実装
- 10.2.2. レイヤごとのテスト実装 | TERASOLUNA
- Spring Bootでテストを書くときのやりかたまとめ | Qiita
- Springで外部APIをリクエストする場合のテスト | 壁にでも話してろ
- Testing your REST controllers and clients with Spring | DIMITR.IM
- Spring Test – How to test a JSON Array in jsonPath | Mkyong.com
■Mockito
Mockito全般
- mockito.org
- 【Java】Mockitoの飲み方(入門) | Orizuru
- Spring Bootでmockitoを使ってテストする | 株式会社CONFRAGE ITソリューション事業部
MockMvc
- 10.2.4.2. MockMvc | TERASOLUNA
- Spring Boot MockMvc not working test with @PathVariable [duplicate] | stack overflow