LoginSignup
19
29

More than 3 years have passed since last update.

【Spring Boot】JUnit×DBUnitでControllerクラス, Serviceクラス, Repositoryクラスをテストする

Last updated at Posted at 2020-06-07

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

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つ。

  1. 「nullCatalogMeansCurrent=true」とすることで、同DBインスタンスの別データベースに同名のテーブルが存在する場合のエラーを回避
  2. 「allow-bean-definition-overriding: true」(上の「main:」も忘れずに)とすることで、beanオーバーライドを許可
    してあげましょう。

build.gradleの設定

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クラス

ItemController.java
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クラス

ItemPostRequest.java
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クラス

ItemResponse.java
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クラス

ItemService.java
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

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クラス

Item.java
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

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

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

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つ。
1. @Mockを用いる代わりに、クラスの上に@SpringBootTestアノテーションを付与
DBUnitの機能により、テストメソッド実行前後に、テストデータとDBデータの入れ替えが行われます。
2. @Transactionalを用いてのテストなので、テスト実行前後に、@BeforeEach/@AfterEachでなく、@BeforeTransaction/@AfterTransactionを用いる(そうしないと特にCUD系の処理の時に、トランザクションロックがかかる可能性があります)

今回、上の階層では使っていませんが、挿入、更新(insert, update)系のコードも書き、テスト後にDBのAUTO_INCREMENTを実行前のものに戻す記述も入れてみました。

ItemRepositoryTest.xlsx

image.png

Repositoryクラスのテスト用のデータです。
今回は、ItemRepositoryTest.javaと同階層に配置して使います。

ポイントは、
1. 1行目にカラム名を記載する
2. シート名はテーブル名にする(今回は「items」)
ことです。

補足事項

  1. Controllerクラスには、画面遷移を伴う@Controllerではなく、WebAPIで用いられる、@RestControllerを用いています。
  2. 今回は各クラスの書き方に重点を置いているので、例外やエラーのテストは省略しています。

本記事の更新履歴

  1. 2020年6月13日: コードをGitHub spring-boot-test-sample に追加。
  2. 2020年6月15日:Entityクラス、Serviceクラスのトランザクション制御に関する記述を追記

参考

■JUnit

JUnit全般

appication.ymlの設定

build.gradleの設定

アノテーション

アサーション

テスト実装

■Mockito

Mockito全般

MockMvc

■DBUnit

DBUnit全般

■その他

Spring

19
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
29