JavaによるCI導入ガイドの第5回は End to End (e2e) テストを行う。
目次
1. パッケージ導入
Geb を導入してE2E試験を行う。 Selenium ドライバは好きなものを利用できるが、今回は Chrome と Firefox を利用することにする。 今回は、e2e
という(gradleの)子プロジェクトを作成し、その中ですでに起動しているアプリケーションサーバに接続する形で試験を行う。
./e2e ディレクトリ内の構成
e2e
├── build.gradle
└── src
└── test
├── groovy
│ └── IndexPageSpec.groovy
└── resources
└── GebConfig.groovy
まず、ビルドスクリプトを用意する。
e2e/build.gradle
buildscript {
repositories {
mavenCentral()
}
}
// Gradle プラグインの定義
plugins {
id "idea"
id "groovy"
id "com.energizedwork.webdriver-binaries" version "1.4"
id "com.energizedwork.idea-base" version "1.4"
}
ext {
// 使用するドライバの定義
// chrome ドライバと Firefox ドライバを利用する
// dev はGUIのある環境でのテストの設定
// ci は GUI のない環境で行うための設定(ヘッドレスブラウザのみを用いる)
drivers = []
availableDrivers = [
dev: ["firefox", "chrome", "firefoxHeadless", "chromeHeadless"],
ci: ["chromeHeadless", "firefoxHeadless"],
]
// 各ライブラリなどのバージョンの設定
ext {
groovyVersion = '2.4.15'
gebVersion = '2.2'
seleniumVersion = '3.14.0'
chromeDriverVersion = '2.41'
geckoDriverVersion = '0.21.0'
}
}
// 共通設定を読み込む
apply from: '../common.gradle'
// 外部に書き出したヘルパメソッド定義を読み込む
apply from: '../helper.gradle'
repositories {
mavenCentral()
}
// Java のバージョン指定
sourceCompatibility = 1.10
targetCompatibility = 1.10
// 依存パッケージ
dependencies {
testCompile "org.gebish:geb-spock:$gebVersion"
testCompile("org.spockframework:spock-core:1.1-groovy-2.4") {
exclude group: "org.codehaus.groovy"
}
testCompile "org.codehaus.groovy:groovy-all:$groovyVersion"
testCompile "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
testCompile "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
}
// webdriver 設定
webdriverBinaries {
chromedriver chromeDriverVersion
geckodriver geckoDriverVersion
}
// -Pci=yes が与えられたらヘッドレスのみのテストの実行を行う
if (hasProperty("ci")) {
drivers = availableDrivers["ci"]
} else {
drivers = availableDrivers["dev"]
}
// 各ドライバでのテストのタスク定義
drivers.each { driver ->
task "${driver}Test"(type: Test) {
group JavaBasePlugin.VERIFICATION_GROUP
outputs.upToDateWhen { false } // Always run tests
systemProperty "geb.build.reportsDir", reporting.file("geb/$name")
systemProperty "geb.env", driver
}
}
// デフォルトのテストタスクは実行しない
test {
enabled = false
}
// e2e テスト用の e2e:e2eTest タスクを設定する
// test タスクのフェーズでは実行しないことにする
task e2eTest {
dependsOn drivers.collect { tasks["${it}Test"] }
}
// メモリ設定など
tasks.withType(Test) {
maxHeapSize = "1g"
jvmArgs '-XX:MaxMetaspaceSize=128m'
testLogging {
exceptionFormat = 'full'
}
}
tasks.withType(GroovyCompile) {
groovyOptions.forkOptions.memoryMaximumSize = '256m'
}
// -Pe2eServerUrl で接続先のURLを指定できるようにする
// 指定がなかった場合は common.gradle の設定を使用する
def e2eServerUrl = project.e2eServerDefaultUrl
if (hasProperty('e2eServerUrl')) {
e2eServerUrl = getProperty('e2eServerUrl')
}
// ドライバタスクに対する依存性の設定など
drivers.each { driver ->
// Geb の接続先URL設定
def task = tasks["${driver}Test"]
task.systemProperty "geb.build.baseUrl", e2eServerUrl
// テストの開始前にアプリケーションサーバの起動を待機する
task.doFirst {
project.waitForStartingHttpServer e2eServerUrl, "/"
}
// build タスクに依存させて jar ファイルが生成されているようにする
task.dependsOn parent.tasks["build"]
}
マルチプロジェクトの設定のために、プロジェクトルートに ./settings.gradle
を作成する。
settings.gradle
include ':e2e'
必要な共通設定の項目を追加する。
common.gradle
diff --git a/common.gradle b/common.gradle
index 0e985a8..e7f4e26 100644
--- a/common.gradle
+++ b/common.gradle
@@ -4,4 +4,13 @@ ext {
// アプリケーションのバージョン
appVersion = "0.1.0"
+
+ // サーバ起動待機回数指定
+ e2eServerStartupWaitCount = 20
+
+ // サーバ起動待機 待ち時間指定
+ e2eServerStartupWaitTime = 500
+
+ // e2e アプリケーションサーバURL
+ e2eServerDefaultUrl = "http://localhost:8080"
}
補助メソッド(サーバ起動待機処理)のためのファイルを追加する。
helper.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.codehaus.groovy.modules.http-builder:http-builder:0.6"
}
}
// HTTP サーバの起動を待つ
def waitForStartingHttpServer(final String server, final String path) {
final http = new groovyx.net.http.HTTPBuilder(server)
final cnt = project.e2eServerStartupWaitCount
final wait = project.e2eServerStartupWaitTime
def success = false
for(final i in 0..<cnt) {
try {
logger.info "sleep $wait ms..."
sleep wait
logger.info "(re)try to connect ${server}..."
http.get(path: path)
success = true
logger.info "successed to connect server!"
break
} catch(final org.apache.http.client.clientprotocolexception e) {
logger.info "failed to connect server. message: ${e.message}"
continue
} catch(final org.apache.http.nohttpresponseexception e) {
logger.info "failed to connect server. message: ${e.message}"
continue
} catch(final exception e) {
throw new runtimeexception(e)
}
}
if (!success) {
throw new gradleexception("unable to check server startup: ${server}.")
}
}
ext {
waitforstartinghttpserver = this.&waitforstartinghttpserver
}
2. テスト実行
まず、Geb の設定ファイルを設置する。 主にドライバの定義などを行っている。
e2e/src/test/resources/GebConfig.groovy
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.firefox.FirefoxBinary
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions
waiting {
timeout = 2
}
environments {
// Chrome GUI ブラウザテスト設定
chrome {
driver = {
ChromeOptions o = new ChromeOptions()
// この2つのオプションは Ubuntu 上でのエラー回避
o.addArguments("--no-sandbox");
o.addArguments("--disable-dev-shm-usage");
new ChromeDriver(o)
}
}
// Chrome ヘッドレスブラウザテスト設定
chromeHeadless {
driver = {
ChromeOptions o = new ChromeOptions()
o.addArguments('headless')
o.addArguments("--no-sandbox");
o.addArguments("--disable-dev-shm-usage");
new ChromeDriver(o)
}
}
// Firefox GUI ブラウザテスト設定
firefox {
atCheckWaiting = 1
driver = { new FirefoxDriver() }
}
// Firefox ヘッドレスブラウザテスト設定
firefoxHeadless {
atCheckWaiting = 1
driver = {
FirefoxBinary firefoxBinary = new FirefoxBinary()
firefoxBinary.addCommandLineOptions("--headless")
FirefoxOptions firefoxOptions = new FirefoxOptions()
firefoxOptions.setBinary(firefoxBinary)
new FirefoxDriver(firefoxOptions)
}
}
}
次に、テストコードを追加する。
src/test/groovy/IndexPageSpec.groovy
import geb.Module
import geb.Page
import geb.spock.GebSpec
import java.util.UUID
import spock.lang.Unroll
// 投稿フォーム用モジュール
class FormModule extends Module {
static content = {
// 送信フォーム
form { $(".message-form form") }
// メッセージ入力ボックス
messageInput { form.$("input", name: "message") }
// 送信ボタン
submitButton(to: IndexPage) { form.$("button", type: "submit") }
}
// テキストボックスにメッセージを入力する
void setMessage(message) {
messageInput = message
}
// メッセージを送信する
void submit() {
submitButton.click()
}
}
// メッセージ表示用モジュール
class MessageViewModule extends Module {
static content = {
view { $("span") }
// メッセージに表示されたテキスト
message { view.text() }
// メッセージのテキストカラー
color { view.css("color") }
}
}
// メッセージ一覧用モジュール
class MessageListModule extends Module {
static content = {
messages { $(".messages-list ul li").moduleList(MessageViewModule) }
}
}
// インデックス画面オブジェクト
class IndexPage extends Page {
static url = "/"
static at = { title == "一行掲示板" }
static content = {
form { module FormModule }
messageList { module MessageListModule }
}
}
// インデックス画面 spec
class IndexPageSpec extends GebSpec {
def message
def setup() {
def id = UUID.randomUUID()
// メッセージを spec 毎にランダムで生成する
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ${id}"
}
def "コマンドが無いメッセージを投稿すると、一番先頭に黒色で表示される"() {
given:
// インデックスページに移動する
to IndexPage
when:
// メッセージ入力テキストボックスにメッセージを入力する
form.setMessage(message)
// 投稿ボタンを押す
form.submit()
then:
// インデックスページに遷移する
at IndexPage
// 投稿一覧の先頭のメッセージは今投稿したメッセージである
messageList.messages[0].message == message
// 文字色を確認する
// css("color") は ブラウザによって rgb/rgba を返す模様
messageList.messages[0].color =~ /rgba?\(0, 0, 0(, 1)?\)/
}
@Unroll
def "#command コマンドが先頭についたメッセージを投稿すると、#colorReg で表示される"() {
given:
to IndexPage
when:
form.setMessage("${command}${message}")
form.submit()
then:
at IndexPage
messageList.messages[0].message == message
messageList.messages[0].color =~ colorReg
where:
command || colorReg
"#red" || /rgba?\(201, 58, 64(, 1)?\)/
"#green" || /rgba?\(86, 167, 100(, 1)?\)/
"#blue" || /rgba?\(0, 116, 191(, 1)?\)/
}
@Unroll
def "コマンド(#command)の後ろの空白は無視される"() {
given:
to IndexPage
when:
form.setMessage("${command} ${message}")
form.submit()
then:
at IndexPage
messageList.messages[0].message == message
where:
command || _
"#red" || _
"#green" || _
"#blue" || _
}
@Unroll
def "コマンド(#command)の前に空白があるとコマンドとして認識されない"() {
given:
to IndexPage
when:
form.setMessage(" ${command}${message}")
form.submit()
then:
at IndexPage
// geb からノードのテキストを受け取るときに空白はトリムされる模様
messageList.messages[0].message == "${command}${message}"
where:
command || _
"#red" || _
"#green" || _
"#blue" || _
}
def "空のメッセージは受け付けない(表示されない)"() {
given:
to IndexPage
when:
form.setMessage(message)
form.submit()
form.setMessage("")
form.submit()
then:
at IndexPage
messageList.messages[0].message == message
}
def "タグのみのメッセージは受け付けない(表示されない)"() {
given:
to IndexPage
when:
form.setMessage(message)
form.submit()
form.setMessage("${command} ")
form.submit()
then:
at IndexPage
messageList.messages[0].message == message
where:
command || _
"#red" || _
"#green" || _
"#blue" || _
}
}
Geb は JQuery ライクの API で DOM にアクセスするが、各要素へのアクセスをモジュールで抽象化する構成になっている。 そのため、テストコードからは実際の DOM 構造が隠蔽され、見通しの良いテストコードを書くことができる。
ブラウザを実際に操作して行うテストであるため、Javascript の実行確認なども行うことができる。 今回のケースでは文字色を確認しているが、これは実際にレンダリングされた色を取得している。
テストを実行してみよう。 まず./gradlew bootRun
でアプリケーションを起動し、別のターミナルから ./gradlew e2e:e2eTest
を実行する。 実行するマシンには Chrome と Firefox がインストールされている必要がある。 GUI ブラウザでのテストのときには、実際にブラウザが立ち上がってテストが行われる様子が確認できるだろう。
今回の実装では行っていないが、データベースへの接続の設定を適切に与えて、データのクリア処理を行えるようにする必要もあるだろう。
実行結果は以下のようになる。
...
IndexPageSpec > 空のメッセージは受け付けない(表示されない) FAILED
geb.error.UnexpectedPageException: At checker page verification failed for page IndexPage
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at geb.navigator.NonEmptyNavigator.click(NonEmptyNavigator.groovy:461)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.navigator.NonEmptyNavigator.click(NonEmptyNavigator.groovy:438)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.content.TemplateDerivedPageContent.click(TemplateDerivedPageContent.groovy:80)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at FormModule.submit(IndexPageSpec.groovy:29)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.content.TemplateDerivedPageContent.methodMissing(TemplateDerivedPageContent.groovy:95)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at IndexPageSpec.空のメッセージは受け付けない(表示されない)(IndexPageSpec.groovy:164)
Caused by:
Assertion failed:
title == "一行掲示板"
| |
"" false
at IndexPage._clinit__closure1(IndexPageSpec.groovy:60)
at IndexPage._clinit__closure1(IndexPageSpec.groovy)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.Page.verifyThisPageAtOnly(Page.groovy:245)
at geb.Page.getAtVerificationResult(Page.groovy:223)
at geb.Page.verifyAt(Page.groovy:194)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.Browser.methodMissing(Browser.groovy:217)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at geb.navigator.NonEmptyNavigator.click(NonEmptyNavigator.groovy:450)
... 13 more
IndexPageSpec > タグのみのメッセージは受け付けない(表示されない) FAILED
Condition not satisfied:
messageList.messages[0].message == message
| | | | | |
| | | "" | Lorem ipsum dolor sit amet, consectetur adipiscing elit. e66b1afe-57f8-4350-b628-db0a94ad9439
| | | false
| | | 93 differences (0% similarity)
| | | (---------------------------------------------------------------------------------------------)
| | | (Lorem ipsum dolor sit amet, consectetur adipiscing elit. e66b1afe-57f8-4350-b628-db0a94ad9439)
| | MessageViewModule
| [MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule]
IndexPage -> messageList: MessageListModule
at IndexPageSpec.タグのみのメッセージは受け付けない(表示されない)(IndexPageSpec.groovy:183)
Condition not satisfied:
messageList.messages[0].message == message
| | | | | |
| | | "" | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 65ec5759-2939-471a-8cc0-25f2fdbbed92
| | | false
| | | 93 differences (0% similarity)
| | | (---------------------------------------------------------------------------------------------)
| | | (Lorem ipsum dolor sit amet, consectetur adipiscing elit. 65ec5759-2939-471a-8cc0-25f2fdbbed92)
| | MessageViewModule
| [MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule]
IndexPage -> messageList: MessageListModule
at IndexPageSpec.タグのみのメッセージは受け付けない(表示されない)(IndexPageSpec.groovy:183)
Condition not satisfied:
messageList.messages[0].message == message
| | | | | |
| | | "" | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 86237fc8-ab63-4afa-b9a9-5329165a969a
| | | false
| | | 93 differences (0% similarity)
| | | (---------------------------------------------------------------------------------------------)
| | | (Lorem ipsum dolor sit amet, consectetur adipiscing elit. 86237fc8-ab63-4afa-b9a9-5329165a969a)
| | MessageViewModule
| [MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule, MessageViewModule]
IndexPage -> messageList: MessageListModule
at IndexPageSpec.タグのみのメッセージは受け付けない(表示されない)(IndexPageSpec.groovy:183)
12 tests completed, 2 failed
> Task :e2e:chromeHeadlessTest FAILED
FAILURE: Build failed with an exception.
...
空のメッセージの処理が失敗していることがわかる。そもそもバリデーションを実装していなかった。何ということだ。 急いでバリデーションを実装しよう。
src/main/java/com/example/messageboard/MessageForm.java
diff --git a/src/main/java/com/example/messageboard/MessageForm.java b/src/main/java/com/example/messageboard/MessageForm.java
index 39cd4a8..9a7d5f2 100644
--- a/src/main/java/com/example/messageboard/MessageForm.java
+++ b/src/main/java/com/example/messageboard/MessageForm.java
@@ -1,6 +1,8 @@
package com.example.messageboard;
import android.support.annotation.Nullable;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
/**
@@ -9,6 +11,11 @@ import android.support.annotation.Nullable;
public class MessageForm {
@Nullable
+ @Pattern(
+ regexp = "^((?!#(red|green|blue)\\s*$).*)|\\s*$",
+ flags = Pattern.Flag.MULTILINE,
+ message = "コマンドだけのメッセージは投稿できません")
+ @NotBlank(message = "空のメッセージは投稿できません")
private String message;
/**
src/main/java/com/example/messageboard/MessageBoardController.java
diff --git a/src/main/java/com/example/messageboard/MessageBoardController.java b/src/main/java/com/example/messageboard/MessageBoardController.java
index 87db1c4..a318a8f 100644
--- a/src/main/java/com/example/messageboard/MessageBoardController.java
+++ b/src/main/java/com/example/messageboard/MessageBoardController.java
@@ -4,6 +4,7 @@ import java.time.ZonedDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
+import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -49,10 +50,7 @@ public class MessageBoardController {
public ModelAndView index(
@ModelAttribute("messageForm") final MessageForm messageForm,
final ModelAndView mav) {
- mav.setViewName("index");
- mav.addObject(
- "messages", this.messagesRepository.findAllOrderByCreatedAtDesc());
- return mav;
+ return this.showIndex(mav);
}
/**
@@ -70,10 +68,25 @@ public class MessageBoardController {
@RequestMapping(value = "/", method = RequestMethod.POST)
@Transactional(readOnly = false)
public ModelAndView create(
- @ModelAttribute("messageForm") final MessageForm messageForm,
+ @ModelAttribute("messageForm") @Validated final MessageForm messageForm,
+ final BindingResult result,
final ModelAndView mav) {
- var message = this.messageMappingService.map(messageForm, ZonedDateTime.now());
- this.messagesRepository.saveAndFlush(message);
- return new ModelAndView("redirect:/");
+ if (!result.hasErrors()) {
+ var message = this.messageMappingService.map(messageForm, ZonedDateTime.now());
+ this.messagesRepository.saveAndFlush(message);
+ return new ModelAndView("redirect:/");
+ } else {
+ return this.showIndex(mav);
+ }
+ }
+
+ /**
+ * 一覧表示画面のレンダリングを行う.
+ */
+ protected ModelAndView showIndex(final ModelAndView mav) {
+ mav.setViewName("index");
+ mav.addObject(
+ "messages", this.messagesRepository.findAllOrderByCreatedAtDesc());
+ return mav;
}
}
src/main/resources/templates/index.html
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index dae9b46..485ee2e 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -54,6 +54,7 @@
<h1>一行掲示板</h1>
<section class="message-form">
<form action="/" method="POST" th:object="${messageForm}">
+ <div th:if="${#fields.hasErrors('message')}" class="errors" th:errors="*{message}" />
<input type="text" name="message" placeholder="メッセージを入力" th:value="*{message}">
<button type="submit">投稿</button>
</form>
これで空のメッセージとコマンドだけのメッセージのときはエラー表示を行うようになった。 バリデーションのユニットテストを追加しよう。
src/test/groovy/com/example/messageboard/MessageFormSpec.groovy
更に、エラー時のリクエスト処理のテストも追加する。
src/test/groovy/com/example/messageboard/MessageBoardControllerSpec.groovy
diff --git a/src/test/groovy/com/example/messageboard/MessageBoardControllerSpec.groovy b/src/test/groovy/com/example/messageboard/MessageBoardControllerSpec.groovy
index 03afe24..9b5d240 100644
--- a/src/test/groovy/com/example/messageboard/MessageBoardControllerSpec.groovy
+++ b/src/test/groovy/com/example/messageboard/MessageBoardControllerSpec.groovy
@@ -51,4 +51,32 @@ class MessageBoardControllerSpec extends Specification {
})
responce.andExpect(redirectedUrl("/"))
}
+
+ @Unroll
+ def "POST / は正しくないデータ(\"message=#message\")を受け取ったときに、データを登録しないで 200 を返す"() {
+ when:
+ def responce = mockMvc.perform(post("/").param("message", message))
+
+ then:
+ responce.andExpect(status().isOk())
+ 0 * messageMappingServiceMock.map(*_)
+ 0 * messagesRepositoryMock.saveAndFlush(_)
+
+ where:
+ message || _
+ "" || _
+ "#red" || _
+ "#green" || _
+ "#blue" || _
+ }
+
+ def "POST / は message を受け取らなかったときに、データを登録しないで 200 を返す"() {
+ when:
+ def responce = mockMvc.perform(post("/").param("no-message", "hoge"))
+
+ then:
+ responce.andExpect(status().isOk())
+ 0 * messageMappingServiceMock.map(*_)
+ 0 * messagesRepositoryMock.saveAndFlush(_)
+ }
}
まずユニットテストと各種解析を確認して、再び e2e テストを実施する。今度は通過する。
次回
社内サーバにリモートリポジトリを作るのも一つですが、「開発にまつわる面倒事」をこの際全部、tracpath(トラックパス)に任せてみませんか?
バージョン管理サービス・プロジェクト管理サービスの「tracpath(トラックパス)」では、
ユーザー5名、リポジトリ数3つまで、無料で利用可能です。
さっそく実務でも使って見ましょう。
自らも開発を行う会社が作ったからこそ、開発チームの「作る情熱」を支える、やるべきことに集中出来るサービスになっています。
エンタープライズ利用が前提のASPサービスなので、セキュリティも強固です。