ユニットテスト環境にJUnit5とWireMockを使ったプロジェクトのひな型のメモ

# 概要

この記事は、ユニットテスト環境にJUnit5とMockサーバにWireMockを使ったプロジェクトのひな型を作った時のメモです。

**環境**

* Windows 10 Professional
* OpenJDK 11
* JUnit5 5.3.2
* WireMock 2.20.0
* Maven 3.6.0
* Eclipse 2018-09

**参考**

* JUnit5
    * [JUnit 5 User Guide] (https://junit.org/junit5/docs/current/user-guide/)
* WireMock
    * [WireMock] (http://wiremock.org/)
    * [WireMock JUnit 5 and Rest-Assured Example] (https://www.swtestacademy.com/wiremock-junit-5-rest-assured/)
* REST Assured
    * [Usage rest-assured/rest-assured] (https://github.com/rest-assured/rest-assured/wiki/Usage)

# JDK 11

```
> java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
```

# Maven

[Release Notes – Maven – Version 3.6.0] (https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12316922&version=12338966)

```
D:\dev\apache-maven-3.6.0> bin\mvn.cmd -v
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00)
Maven home: D:\dev\apache-maven-3.6.0\bin\..
Java version: 11.0.1, vendor: Oracle Corporation, runtime: D:\openjdk\openjdk-11.0.1_windows-x64
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
```

# pom.xml

最終的なpom.xmlの内容を掲載します。

```xml
<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>

  <groupId>com.example</groupId>
  <artifactId>wiremock-demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>wiremock-demo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>11</java.version>
    <!-- compiler -->
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>
    <maven.compiler.verbose>true</maven.compiler.verbose>
    <!-- JUnit5 -->
    <junit.jupiter.version>5.3.2</junit.jupiter.version>
    <junit.platform.version>1.3.2</junit.platform.version>
    <!-- WireMock -->
    <wiremock.version>2.20.0</wiremock.version>
    <!-- REST Assured -->
    <rest-assured.version>3.2.0</rest-assured.version>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher -->
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <version>${junit.platform.version}</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.github.tomakehurst/wiremock -->
    <dependency>
      <groupId>com.github.tomakehurst</groupId>
      <artifactId>wiremock</artifactId>
      <version>2.20.0</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <version>${rest-assured.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>demo</finalName>
    <plugins>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-clean-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-clean-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <compilerArgs>
            <arg>-Xlint:all</arg>
          </compilerArgs>
          <release>${java.version}</release>
        </configuration>
      </plugin>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-resources-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-jar-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.1</version>
      </plugin>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
        <!-- <configuration> <forkCount>0</forkCount> </configuration> -->
      </plugin>
      <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-failsafe-plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>

</project>
```

## JUnit5

### [junit-jupiter-engine] (https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine)

```xml
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.3.2</version>
    <scope>test</scope>
</dependency>
```

### [junit-platform-launcher] (https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher)

```xml
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher -->
<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.3.2</version>
    <scope>test</scope>
</dependency>
```

## [WireMock] (http://wiremock.org/)

### [wiremock] (https://mvnrepository.com/artifact/com.github.tomakehurst/wiremock)

```xml
<!-- https://mvnrepository.com/artifact/com.github.tomakehurst/wiremock -->
<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock</artifactId>
    <version>2.20.0</version>
    <type>pom</type>
    <scope>test</scope>
</dependency>
```

### [wiremock-junit5] (https://mvnrepository.com/artifact/ru.lanwen.wiremock/wiremock-junit5)

今回は使いませんでしたが、Third partyのJUnit5拡張機能もあります。

```xml
<!-- https://mvnrepository.com/artifact/ru.lanwen.wiremock/wiremock-junit5 -->
<dependency>
    <groupId>ru.lanwen.wiremock</groupId>
    <artifactId>wiremock-junit5</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>
```

## [REST Assured] (http://rest-assured.io/)

WireMockのスタブの動作確認用にREST Assuredを使用しました。

### [rest-assured] (https://mvnrepository.com/artifact/io.rest-assured/rest-assured)

```xml
<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>3.2.0</version>
    <scope>test</scope>
</dependency>
```

# WireMockの基本的な使い方

## WireMockServer

現時点(2019/01)でJUnit5の拡張機能は用意されていないので、[Java (Non-JUnit) Usage] (http://wiremock.org/docs/java-usage/)を参考に、WireMockサーバをプログラム的(programmatically)に起動、停止させます。

```java
@DisplayName("WireMock Test Demo")
public class WireMockDemoTests {

  private WireMockServer server;
  private RestAssuredConfig config;

  @BeforeEach
  void setup() {
    // REST Assured configuration
    config = config().encoderConfig(encoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false))
                     .logConfig(logConfig().enablePrettyPrinting(true));

    server = new WireMockServer();
    server.start();
  }

  @AfterEach
  void tearDown() {
    server.stop();
  }

}
```

### [Configuration] (http://wiremock.org/docs/configuration/)

```java
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
```

```java
// ConsoleNotifier
Notifier notifier = new ConsoleNotifier(true);
// Slf4jNotifier
// Notifier notifier = new Slf4jNotifier(true);

Options options = options().port(8080).notifier(notifier);
server = new WireMockServer(options);
```

## [Stubbing] (http://wiremock.org/docs/stubbing/)

スタブの登録方法

### 基本的なスタブの登録

```java
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
```

```java
@Test
@DisplayName("wiremock demo #1")
void mockDemo1() {

	// stub (WireMock)
	server.stubFor(get(urlEqualTo("/mock/demo/1")).withName("mock_demo_1")
			.willReturn(
					aResponse()
						.withStatus(200)
						.withHeader("Content-Type", "text/plain")
						.withBody("Hello world!"))
			);

	// exercise and assert (REST Assured)
	given()
		.config(config)
		.header("Content-Type", "text/plain")
		.when()
			.get("http://localhost:8080/mock/demo/1")
		.then()
			.assertThat()
				.contentType(ContentType.TEXT)
				.statusCode(200)
				.header("Matched-Stub-Name", "mock_demo_1")
				.body(Matchers.containsString("Hello world!"));

	// verify (WireMock)
	server.verify(getRequestedFor(urlEqualTo("/mock/demo/1"))
	        .withHeader("Content-Type", equalTo("text/plain")));
}
```

### マッピングファイルを使う

デフォルトでは`src/test/resources/mappings`ディレクトリ下に配置するマッピングファイル（拡張子が`json`）からスタブのマッピングが登録されます。

#### マッピングファイルの例

requestとresponseにスタブの振る舞いを記述します。
nameはオプションですが明記しておくと`Matched-Stub-Name`というレスポンスヘッダーにnameがセットされるようになります。

```json:demo2.json
{
	"name": "mock_demo_2",
	"request": {
		"method": "GET",
		"url": "/mock/demo/2",
		"headers": {
			"Content-Type": {
				"matches": "application/json"
			}
		}
	},
	"response": {
		"status": 200,
		"body": "{\"name\": \"John\"}",
		"headers": {
			"Content-Type": "application/json"
		}
	}
}
```

マッピングファイルを利用することで、テストにスタブの設定コードを記述しなくても済むようになります。

```java
@Test
@DisplayName("wiremock demo #2")
void mockDemo2() {

	// exercise and assert (REST Assured)
	given()
		.config(config)
		.header("Content-Type", "application/json")
		.when()
			.get("http://localhost:8080/mock/demo/2")
		.then()
			.assertThat()
				.contentType(ContentType.JSON)
				.statusCode(200)
				.header("Matched-Stub-Name", "mock_demo_2")
				.body("name", Matchers.equalTo("John"));

	// verify (WireMock)
	server.verify(getRequestedFor(urlEqualTo("/mock/demo/2"))
	        .withHeader("Content-Type", equalTo("application/json")));
}
```

#### Response Bodyに外部ファイルを利用する

Response Bodyの内容に外部ファイルを利用したい場合、bodyの代わりにbodyFileNameにファイル名を指定します。

```json:demo3.json
{
	"name": "mock_demo_3",
	"request": {
		"method": "GET",
		"url": "/mock/demo/3",
		"headers": {
			"Content-Type": {
				"matches": "application/json"
			}
		}
	},
	"response": {
		"status": 200,
		"bodyFileName": "loto.json",
		"headers": {
			"Content-Type": "application/json"
		}
	}
}
```

デフォルトのファイルの設置場所は`src/test/resources/__files`になります。

```json:loto.json
{
	"lotto": {
		"lottoId": 5,
		"winning-numbers": [
			2,
			45,
			34,
			23,
			7,
			5,
			3
		],
		"winners": [
			{
				"winnerId": 23,
				"numbers": [
					2,
					45,
					34,
					23,
					3,
					5
				]
			},
			{
				"winnerId": 54,
				"numbers": [
					52,
					3,
					12,
					11,
					18,
					22
				]
			}
		]
	}
}
```