前回のエントリ「SpringBoot+Doma2+Gradleを試してみた。」のテストを書いてみたので公開してみる。
RestAPI → RestAssured
Webアプリ → Geb
もちろん題材は「はじめてのSpring Boot」にある顧客管理システムです。
ソースコードはこちら。
https://github.com/nyasba/domaboot
RESTAPI部分
ここは本に出てくる内容とほぼ同じです。
準備
rest-assuredをgradleの依存関係に追加するだけです。
testCompile("com.jayway.restassured:rest-assured:2.3.3")
テストコード
一覧取得
RestAPIはListで返却されるので、パラメータに[0],[1]などのindexが必要です。
@Test
public void 一覧取得を行う() {
when()
.get("/api/customers")
.then()
.statusCode(HttpStatus.OK.value())
.body("id[0]", is(customer1.getId()))
.body("lastName[0]", is(customer1.getLastName()))
.body("firstName[0]", is(customer1.getFirstName()))
.body("id[1]", is(customer2.getId()))
.body("lastName[1]", is(customer2.getLastName()))
.body("firstName[1]", is(customer2.getFirstName()));
}
ID指定取得を行う
こちらはListではないのでパラメータ名そのままでOK
@Test
public void ID指定取得を行う() {
when()
.get("/api/customers/{id}", 101)
.then()
.statusCode(HttpStatus.OK.value())
.body("id", is(customer1.getId()))
.body("lastName", is(customer1.getLastName()))
.body("firstName", is(customer1.getFirstName()));
}
新規登録を行う
ContentTypeは指定するメソッドがあるようです。
文字コード指定はconfigでEncorderConfigがあるのでそれで指定したらできました。
body要素はEntityオブジェクトをそのまま渡す形でいけるようですが、今回はこれでテストしました。
@Test
public void 新規登録を行う() {
CustomerEntity SIZUKA_CHAN = new CustomerEntity(1, "宮本", "しずか");
given()
.contentType(ContentType.JSON)
.config(getUTF8Config())
.body(String.format("{ \"lastName\":\"%s\", \"firstName\":\"%s\" }", SIZUKA_CHAN.getLastName(), SIZUKA_CHAN.getFirstName()))
.when()
.post("/api/customers")
.then()
.statusCode(HttpStatus.CREATED.value());
assertThat(customerRepository.findById(1), is(SIZUKA_CHAN));
}
private RestAssuredConfig getUTF8Config() {
return new RestAssuredConfig().encoderConfig(
EncoderConfig.encoderConfig().defaultContentCharset("UTF-8")
);
}
更新
@Test
public void 更新を行う() {
CustomerEntity DORAMI = new CustomerEntity(101, "どら", "ミ");
given()
.contentType(ContentType.JSON)
.config(getUTF8Config())
.body(String.format("{ \"lastName\":\"%s\", \"firstName\":\"%s\" }", DORAMI.getLastName(), DORAMI.getFirstName()))
.when()
.put("/api/customers/{id}", 101)
.then()
.statusCode(HttpStatus.OK.value());
assertThat(customerRepository.findById(101), is(DORAMI));
}
削除
@Test
public void 削除を行う() {
when()
.delete("/api/customers/{id}", 101)
.then()
.statusCode(HttpStatus.NO_CONTENT.value());
assertThat(customerRepository.findById(101), is(nullValue()));
}
Webアプリ部分
Geb(じぇぶ)を使います。GebはWebアプリのブラウザテストを実行するためのフレームワークで、groovyを使って記述します。seleniumを使ったテストを簡単に書けるもの、といった方がわかりやすいかもしれません。
準備
groovyを使うので、javaからgroovyに変えます
apply plugin: 'groovy'
依存関係には以下を追加。
/* SpockFramework(GroovyのBDDテストフレームワーク) */
testCompile("org.spockframework:spock-core:0.7-groovy-2.0")
/* Geb(ブラウザテスト用フレームワーク) */
testCompile "org.gebish:geb-spock:0.10.0"
/* Webドライバ */
testCompile "org.seleniumhq.selenium:selenium-chrome-driver:2.44.0"
Gebの設定用オブジェクト。
Chromeで実行するための設定です。chromedriverはここから各環境に適したものをダウンロードしておく必要があります。
import org.openqa.selenium.chrome.ChromeDriver
System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe")
driver = {
return new ChromeDriver()
}
テストコード
PageObjectパターンを使って実装しました。
メインページのPageObject
package com.example.web.page
import geb.Page
/**
* メインページ
*/
class MainPage extends Page {
// to MainPage でアクセスするURLを定義
static url = "http://localhost:8888/customers"
// at MainPage で満たすべきassert条件を記載
static at = { title == "顧客管理システム" }
// MainPageのコンテンツ
static content = {
姓は { $('#lastName').value(it) }
名は { $('#firstName').value(it) }
で登録する { $('#register').click() }
登録件数 {
$('#customer-list').find("tbody").children().size()
}
名前 {
$("#lastName${it}").text() + $("#firstName${it}").text()
}
編集ボタンを押す(to: EditPage){
$("#edit${it}").click()
}
削除ボタンを押す(to: MainPage){
$("#delete${it}").click()
}
}
}
編集ページのPageObject
package com.example.web.page
import geb.Page
/**
* 編集ページ
*/
class EditPage extends Page {
// at MainPage で満たすべきassert条件を記載
static at = { title == "顧客情報編集" }
// MainPageのコンテンツ
static content = {
姓は { $('#lastName').value(it) }
名は { $('#firstName').value(it) }
で更新する( to : MainPage) { $('#submit').click() }
やっぱり戻る { $('#goToTop').click() }
}
}
テストシナリオ
実行結果から紹介します。
この順に実行されますので、メソッド名と比較して確認していただけるとわかりやすいかと思います。
setupSpec()はテストの一番最初にだけ実行されます。その中でWebアプリを立ち上げています。(これがテストとして適した方法かはわかってません)
あと、テストコード中の「で登録する」や「で更新する」というのはPageクラスで定義したメソッドですので。
package com.example.web.story
import com.example.App
import com.example.web.page.EditPage
import com.example.web.page.MainPage
import geb.spock.GebSpec
import spock.lang.Stepwise
import spock.lang.Unroll
@Unroll // メソッド名に変数を差し込めるようにする
@Stepwise // 上から順に実行するようにする
class CustomerGeb extends GebSpec {
def setupSpec() {
App.main([
"--server.port=8888",
"--spring.profiles.active=test"
] as String[])
}
// whereブロックの変数名を引数に設定。それらをINPUTにメソッドが繰り返し呼ばれる
def "「#lastName#firstName」を登録する"(int num, String lastName, String firstName) {
setup:
to MainPage
at MainPage
assert 登録件数 == num -1
expect:
姓は lastName
名は firstName
で登録する
assert 登録件数 == num
assert 名前(num) == lastName + firstName
// テストに必要なINPUTを定義するブロック
where:
num | lastName | firstName
1 | "剛田" | "たけし"
2 | "どら" | "えもん"
}
def "_#num番目を「#lastName#firstName」に変更する"(int num, String lastName, String firstName){
setup:
to MainPage
at MainPage
expect:
編集ボタンを押す(num)
at EditPage
姓は lastName
名は firstName
で更新する
at MainPage
assert 名前(num) == lastName + firstName
where:
num | lastName | firstName
1 | "きれいな" | "ジャイアン"
}
def "_#num番目を「#lastName#firstName」に変更しようとしてやっぱり戻る"(int num, String lastName, String firstName){
setup:
to MainPage
at MainPage
expect:
def 編集前の名前 = 名前(num)
編集ボタンを押す(num)
at EditPage
姓は lastName
名は firstName
やっぱり戻る
at MainPage
assert 名前(num) == 編集前の名前
where:
num | lastName | firstName
2 | "どら" | "みちゃん"
}
def "_1番目を削除する"(){
setup:
to MainPage
at MainPage
when:
assert 登録件数 == 2
def 編集前の名前 = 名前(2)
削除ボタンを押す(1)
then:
at MainPage
assert 登録件数 == 1
assert 名前(1) == 編集前の名前
}
}
おわり。