【Spring Boot】使用JUnit×DBUnit进行Controller类、Service类和Repository类的测试

这是使用JUnit和DBUnit来测试Spring Boot的Controller类、Service类和Repository类的示例代码。

环境

    • 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语句,创建表。

应用程序配置文件


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的信息。
重点有以下两点。

    1. 通过将”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. 控制器类

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. 请求(Request)类

package jp.co.spring_boot_test_item.app.request;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ItemPostRequest {

    private Integer categoryId;

}

这个方法用于获取使用@PostMapping注解修饰的Controller类中的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 class)

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. 商品仓库.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. 实体类

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;
    }
}

这是一个实体类。
这次我们使用Hibernate ORM基于数据库表定义来生成它。

测试类的实现

1. 项目控制器测试.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,使用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 类的测试。
重点有以下两个。
1. 替代使用 @Mock,而是在类上添加 @SpringBootTest 注解。
通过 DBUnit 的功能,在测试方法执行前后,会进行测试数据和数据库数据的互换。
2. 由于是使用 @Transactional 进行的测试,所以在测试执行前后,使用 @BeforeTransaction/@AfterTransaction,而不是 @BeforeEach/@AfterEach(否则在特定的 CUD 类型操作时,可能会出现事务锁的可能性)。

这次,我在上一层没有使用,但是我也写了插入和更新(insert, update)相关的代码,并在测试后加入了将数据库的AUTO_INCREMENT设置回之前状态的描述。

项目仓库测试.xlsx

image.png

这是用于Repository类的测试数据。
这次,我们把它放在ItemRepositoryTest.java的同级目录下来使用。

关键是:
1. 在第一行上写下列名
2. 工作表名应与表名相同(本次使用”items”)

补充说明

    1. 在Controller类中,我们使用的是@RestController,而不是带有页面转换的@Controller,它主要用于WebAPI。

 

    由于我们在这次重点关注各个类的写法,所以省略了异常和错误测试。

本文更新纪录

    1. 2020年6月13日:

 

    1. 在GitHub上添加了代码 spring-boot-test-sample。

2020年6月15日:增加了有关实体类和服务类的事务控制的描述。

请参考我以下所提供的选项。

■JUnit (Java单元测试框架)

JUnit的概念全面。
or
JUnit的范围很广。

    • JUnit5 | junit.org

 

    JUnit5ユーザーガイド

应用程序.yml文件的配置

    • Spring Boot 1.5→2.1 に移行したときのお話 | Qiita

 

    Spring Boot 2.1 でテスト時 @Bean を挿げ替えたかった | Qiita

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.org

 

    • 【Java】Mockitoの飲み方(入門) | Orizuru

 

    Spring Bootでmockitoを使ってテストする | 株式会社CONFRAGE ITソリューション事業部

MockMvc

模拟MVC (MockMvc)

    • 10.2.4.2. MockMvc | TERASOLUNA

 

    Spring Boot MockMvc not working test with @PathVariable [duplicate] | stack overflow

■ DBUnit 数据库单元测试框架

DBUnit常规

    • DBUnit | TECHSCORE

 

    【超初心者向け】DBUnit超入門 | Qiita

其他

春天

    Springの@Transactional(readOnly=true)で読み取り専用のトランザクションにする | 株式会社CONFRAGE ITソリューション事業部
广告
将在 10 秒后关闭
bannerAds