LoginSignup
1
0

More than 3 years have passed since last update.

Spring Boot + Spring Retry のユニットテストに Spring Test + Mockito + JUnit 4 を使う

Last updated at Posted at 2019-07-20

概要

  • Spring Boot + Spring Retry によるサンプルプログラムを Spring Test で自動テストする
  • テストには Spring Test + Mockito + JUnit 4 を使う

動作確認環境

  • macOS Mojave
  • OpenJDK 11.0.2
  • Spring Boot 2.2.0 M4
  • Spring Retry 1.2.4

サンプルプログラムのソースコード一覧

├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── sample
    │           ├── EchoController.java
    │           ├── EchoRepository.java
    │           └── EchoService.java
    └── test
        └── java
            └── sample
                └── EchoServiceTest.java

Maven のビルド用ファイル pom.xml

今回は Maven でビルドを実行する。
pom.xml は Spring Initializr で生成したものをベースとした。
Spring Retry を使用するため dependencies に spring-retry を追加する。また、実行時に AOP クラスが必要なため spring-boot-starter-aop を追加する。
Spring Test を使用するため spring-boot-starter-test を追加する。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.M4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>info.maigo.lab</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sample</name>
    <description>Sample project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.2.4.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

</project>

ソースコード

EchoController.java

Spring Boot による起動エントリクラス (@SpringBootApplication アノテーションを付加) であり、HTTPリクエストを受け取ってレスポンスを返す Controller クラス (@RestController アノテーションを付加) でもある。
Spring Retry を有効にするため @EnableRetry アノテーションを指定している。

package sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
@EnableRetry
@RestController
public class EchoController {

    public static void main(String[] args) {
        SpringApplication.run(EchoController.class, args);
    }

    @Autowired
    private EchoService service;

    @RequestMapping("/{message}")
    public Map<String, Object> index(@PathVariable("message") String message) {
        return new HashMap<String, Object>() {
            {
                put("result", service.echo(message));
            }
        };
    }
}

EchoRepository.java

Repository クラス。

package sample;

import org.springframework.stereotype.Repository;

import java.util.Random;

@Repository
public class EchoRepository {

    public String echo(String message) {
        // 二分の一の確率で例外が発生する
        if (new Random().nextBoolean()) {
            return message;
        } else {
            throw new RuntimeException("Failure");
        }
    }
}

EchoService.java

Service クラス。
Spring Retry のアノテーション @Retryable を指定したメソッド内で RuntimeException が発生した際に3回まで同じメソッドを試行する。

package sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class EchoService {

    @Autowired
    private EchoRepository repository;

    /**
     * 指定したメッセージを返します。
     * たまに失敗しますが、3回まで試行します。
     * @param message メッセージ
     * @return メッセージ
     */
    @Retryable(
            value = {RuntimeException.class},
            maxAttempts = 3,
            backoff = @Backoff(delay = 1000))
    public String echo(String message) {
        System.out.println("EchoService#echo: " + message);
        return repository.echo(message);
    }

    @Recover
    public String recover(RuntimeException e, String message) {
        System.out.println("EchoService#recover: " + message);
        throw e;
    }
}

EchoServiceTest.java

EchoService クラスのテストクラス。
EchoRepository のモックオブジェクトを生成して、EchoService の挙動をテストする。

package sample;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;

@RunWith(SpringRunner.class) // Spring 用の JUnit を使う
@SpringBootTest // Spring Boot を起動してテストする
public class EchoServiceTest {

    @Autowired
    private EchoService service;

    // ApplicationContext にモックを追加する
    // (既存のオブジェクトがあれば置き換える)
    @MockBean
    private EchoRepository mockRepository;

    @Test
    public void testSuccess() {
        // EchoRepository のモックを用意する
        // このモックは正常に値を返す
        String message = "Zero";
        doReturn(message).when(mockRepository).echo(message);
        // テスト
        assertEquals(message, service.echo(message));
    }

    @Test
    public void testFairureSuccess() {
        // EchoRepository のモックを用意する
        // このモックは例外を1回投げたあと、正常に値を返す
        String message = "Uno";
        doThrow(new RuntimeException("Failure"))
            .doReturn(message)
            .when(mockRepository).echo(message);
        // テスト
        assertEquals(message, service.echo(message));
    }

    @Test
    public void testFairureFairureSuccess() {
        // EchoRepository のモックを用意する
        // このモックは例外を2回投げたあと、正常に値を返す
        String message = "Due";
        doThrow(new RuntimeException("Failure"))
            .doThrow(new RuntimeException("Failure"))
            .doReturn(message)
            .when(mockRepository).echo(message);
        // テスト
        assertEquals(message, service.echo(message));
    }

    @Test(expected = RuntimeException.class) // RuntimeException が発生することを想定
    public void testFairureFairureFairure() {
        // EchoRepository のモックを用意する
        // このモックは例外を3回投げる
        String message = "Tre";
        doThrow(new RuntimeException("Failure"))
            .doThrow(new RuntimeException("Failure"))
            .doThrow(new RuntimeException("Failure"))
            .when(mockRepository).echo(message);
        // テスト
        service.echo(message);
    }
}

テストを実行

mvn test コマンドを入力すると、Spring Boot が起動してテストが実行される。

$ mvn test
[INFO] Scanning for projects...
[INFO] 
[INFO] -----------------------< info.maigo.lab:sample >------------------------
[INFO] Building sample 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
(中略)
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.2.0.M4)

(中略)
2019-07-20 20:12:34.687  INFO 25426 --- [           main] sample.EchoServiceTest                   : Started EchoServiceTest in 3.407 seconds (JVM running for 4.983)
EchoService#echo: Due
EchoService#echo: Due
EchoService#echo: Due
EchoService#echo: Tre
EchoService#echo: Tre
EchoService#echo: Tre
EchoService#recover: Tre
EchoService#echo: Uno
EchoService#echo: Uno
EchoService#echo: Zero
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.643 s - in sample.EchoServiceTest
2019-07-20 20:12:39.930  INFO 25426 --- [       Thread-1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  14.650 s
[INFO] Finished at: 2019-07-20T20:12:40+09:00
[INFO] ------------------------------------------------------------------------

参考資料

1
0
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
1
0