Vert.x
JVM上のリアクティブアプリケーション開発ツールキット。イベント駆動・非ブロッキングアーキテクチャによる高性能・高並行性を実現。
GitHub概要
eclipse-vertx/vert.x
Vert.x is a tool-kit for building reactive applications on the JVM
スター14,521
ウォッチ521
フォーク2,091
作成日:2011年6月17日
言語:Java
ライセンス:Other
トピックス
concurrencyevent-loophigh-performancehttp2javajvmnettynionon-blockingreactivevertx
スター履歴
データ取得日時: 2025/7/17 10:32
フレームワーク
Eclipse Vert.x
概要
Eclipse Vert.xは、JVM上でリアクティブアプリケーションを構築するためのイベント駆動型、ノンブロッキングツールキットです。
詳細
Vert.xはEclipse Foundationによって開発・保守されているオープンソースプロジェクトです。2024年現在も活発に開発が続けられており、TechEmpower Web Framework Benchmarksでは常にトップ5にランクインし、Express.js、ASP.NET Core、Spring Bootなどの著名フレームワークを上回るパフォーマンスを実証しています。
主要なアーキテクチャの特徴:
- リアクティブ・ノンブロッキング: イベントループモデルによる高並行性処理
- Verticleアーキテクチャ: 独立してデプロイ・スケール可能なモジュール設計
- イベントバス: Verticle間の非同期メッセージング機構
- ポリグロット対応: Java、JavaScript、Groovy、Ruby、Scala、Kotlin対応
- ツールキット方式: 必要な機能のみを選択して組み合わせ可能
Vert.xは「フレームワーク」ではなく「ツールキット」として設計されており、アプリケーション構造に関して強い制約を課さず、開発者が必要なモジュールとクライアントを選択して自由に組み合わせることができます。
メリット・デメリット
メリット
- 卓越したパフォーマンス: 最新ベンチマークでSpring Boot比較で大幅な高速化を実現
- 高並行性処理: 少数スレッドで多数の並行リクエストを効率的に処理
- リソース効率: 従来のブロッキングI/Oベースフレームワークより少ないCPU・メモリ消費
- ポリグロット: 複数のJVM言語サポートで既存コードベースとの統合が容易
- モジュラー設計: 必要機能のみを選択でき、軽量で柔軟なアーキテクチャ
- 包括的エコシステム: Web API、データベース、メッセージング、クラウド対応の完全なスタック
- 学習コストの低さ: 比較的習得しやすい設計で新規開発者にもアクセシブル
- コンテナ最適化: 仮想マシンやコンテナなどの制約環境での優れた動作
デメリット
- コード複雑性: ノンブロッキングコードの読み書き・デバッグが困難
- コールバック地獄: 多数のネストしたコールバックによる可読性低下の危険性
- ブロッキング操作の注意: 誤ってブロッキングコードを含めてしまうリスク
- 簡単なアプリには過剰: 高並行性や低レイテンシが不要なCRUDアプリには不向き
- リアクティブプログラミング学習コスト: 概念理解とブロッキング操作回避のための専門知識が必要
- デバッグの困難さ: 非同期処理のデバッグが複雑になる場合がある
参考ページ
- Eclipse Vert.x公式サイト
- Vert.x GitHub リポジトリ
- Vert.x公式ドキュメント
- Vert.x Examples
- Red Hat Developer - Vert.x
- InfoQ - Reactive Applications with Vert.x
書き方の例
Hello World
// HelloWorldVerticle.java
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;
public class HelloWorldVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
HttpServer server = vertx.createHttpServer();
server.requestHandler(request -> {
HttpServerResponse response = request.response();
response
.putHeader("content-type", "text/plain; charset=utf-8")
.end("Hello World from Vert.x!");
});
server.listen(8080, result -> {
if (result.succeeded()) {
System.out.println("サーバーがポート8080で起動しました");
startPromise.complete();
} else {
System.out.println("サーバー起動に失敗しました: " + result.cause());
startPromise.fail(result.cause());
}
});
}
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new HelloWorldVerticle());
}
}
RESTful APIとルーティング
// UserApiVerticle.java
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public class UserApiVerticle extends AbstractVerticle {
private Map<Long, JsonObject> users = new HashMap<>();
private AtomicLong idCounter = new AtomicLong(1);
@Override
public void start(Promise<Void> startPromise) throws Exception {
Router router = Router.router(vertx);
// ボディパーサーを追加
router.route().handler(BodyHandler.create());
// サンプルデータの初期化
initializeData();
// ルート定義
router.get("/").handler(this::handleRoot);
router.get("/api/users").handler(this::getAllUsers);
router.get("/api/users/:id").handler(this::getUser);
router.post("/api/users").handler(this::createUser);
router.put("/api/users/:id").handler(this::updateUser);
router.delete("/api/users/:id").handler(this::deleteUser);
vertx.createHttpServer()
.requestHandler(router)
.listen(8080, result -> {
if (result.succeeded()) {
System.out.println("User API サーバーがポート8080で起動しました");
startPromise.complete();
} else {
startPromise.fail(result.cause());
}
});
}
private void initializeData() {
users.put(1L, new JsonObject()
.put("id", 1)
.put("name", "田中太郎")
.put("email", "[email protected]")
.put("age", 30));
users.put(2L, new JsonObject()
.put("id", 2)
.put("name", "佐藤花子")
.put("email", "[email protected]")
.put("age", 25));
idCounter.set(3);
}
private void handleRoot(RoutingContext context) {
context.response()
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("message", "User API へようこそ")
.put("endpoints", new JsonArray()
.add("GET /api/users")
.add("GET /api/users/:id")
.add("POST /api/users")
.add("PUT /api/users/:id")
.add("DELETE /api/users/:id"))
.encode());
}
private void getAllUsers(RoutingContext context) {
JsonArray userArray = new JsonArray();
users.values().forEach(userArray::add);
context.response()
.putHeader("content-type", "application/json; charset=utf-8")
.end(userArray.encode());
}
private void getUser(RoutingContext context) {
String idParam = context.request().getParam("id");
try {
Long id = Long.parseLong(idParam);
JsonObject user = users.get(id);
if (user != null) {
context.response()
.putHeader("content-type", "application/json; charset=utf-8")
.end(user.encode());
} else {
context.response()
.setStatusCode(404)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "ユーザーが見つかりません")
.encode());
}
} catch (NumberFormatException e) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "無効なユーザーID")
.encode());
}
}
private void createUser(RoutingContext context) {
JsonObject body = context.getBodyAsJson();
if (body == null || !isValidUser(body)) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "無効なユーザーデータ")
.encode());
return;
}
Long id = idCounter.getAndIncrement();
JsonObject user = new JsonObject()
.put("id", id)
.put("name", body.getString("name"))
.put("email", body.getString("email"))
.put("age", body.getInteger("age"));
users.put(id, user);
context.response()
.setStatusCode(201)
.putHeader("content-type", "application/json; charset=utf-8")
.end(user.encode());
}
private void updateUser(RoutingContext context) {
String idParam = context.request().getParam("id");
JsonObject body = context.getBodyAsJson();
if (body == null || !isValidUser(body)) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "無効なユーザーデータ")
.encode());
return;
}
try {
Long id = Long.parseLong(idParam);
if (users.containsKey(id)) {
JsonObject user = new JsonObject()
.put("id", id)
.put("name", body.getString("name"))
.put("email", body.getString("email"))
.put("age", body.getInteger("age"));
users.put(id, user);
context.response()
.putHeader("content-type", "application/json; charset=utf-8")
.end(user.encode());
} else {
context.response()
.setStatusCode(404)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "ユーザーが見つかりません")
.encode());
}
} catch (NumberFormatException e) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "無効なユーザーID")
.encode());
}
}
private void deleteUser(RoutingContext context) {
String idParam = context.request().getParam("id");
try {
Long id = Long.parseLong(idParam);
if (users.remove(id) != null) {
context.response()
.setStatusCode(204)
.end();
} else {
context.response()
.setStatusCode(404)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "ユーザーが見つかりません")
.encode());
}
} catch (NumberFormatException e) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json; charset=utf-8")
.end(new JsonObject()
.put("error", "無効なユーザーID")
.encode());
}
}
private boolean isValidUser(JsonObject user) {
return user.getString("name") != null && !user.getString("name").trim().isEmpty() &&
user.getString("email") != null && !user.getString("email").trim().isEmpty() &&
user.getInteger("age") != null && user.getInteger("age") > 0;
}
}
イベントバスとVerticle間通信
// EventBusExampleVerticle.java
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonObject;
public class EventBusExampleVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
EventBus eventBus = vertx.eventBus();
// メッセージコンシューマーの登録
eventBus.consumer("user.service", this::handleUserService);
eventBus.consumer("notification.service", this::handleNotification);
// HTTPサーバーの起動
vertx.createHttpServer()
.requestHandler(request -> {
String path = request.path();
if (path.equals("/send-message")) {
// イベントバス経由でメッセージ送信
JsonObject message = new JsonObject()
.put("action", "get_user")
.put("userId", 123);
eventBus.request("user.service", message, reply -> {
if (reply.succeeded()) {
request.response()
.putHeader("content-type", "application/json")
.end(reply.result().body().toString());
} else {
request.response()
.setStatusCode(500)
.end("エラー: " + reply.cause().getMessage());
}
});
} else if (path.equals("/publish")) {
// パブリッシュ/サブスクライブモデル
JsonObject notification = new JsonObject()
.put("type", "info")
.put("message", "システム通知です");
eventBus.publish("notification.service", notification);
request.response()
.putHeader("content-type", "text/plain")
.end("通知を送信しました");
} else {
request.response()
.putHeader("content-type", "text/plain")
.end("イベントバス例: /send-message, /publish");
}
})
.listen(8080, result -> {
if (result.succeeded()) {
System.out.println("EventBusサーバーがポート8080で起動しました");
startPromise.complete();
} else {
startPromise.fail(result.cause());
}
});
}
private void handleUserService(Message<JsonObject> message) {
JsonObject body = message.body();
String action = body.getString("action");
if ("get_user".equals(action)) {
Integer userId = body.getInteger("userId");
// ユーザーデータ取得のシミュレーション
JsonObject user = new JsonObject()
.put("id", userId)
.put("name", "ユーザー" + userId)
.put("email", "user" + userId + "@example.com")
.put("timestamp", System.currentTimeMillis());
message.reply(user);
} else {
message.fail(400, "不明なアクション: " + action);
}
}
private void handleNotification(Message<JsonObject> message) {
JsonObject notification = message.body();
String type = notification.getString("type");
String msg = notification.getString("message");
// 通知処理のシミュレーション
System.out.println("[" + type.toUpperCase() + "] " + msg);
// 他のサービスにも転送
if ("info".equals(type)) {
vertx.setTimer(1000, id -> {
System.out.println("通知処理が完了しました: " + msg);
});
}
}
}
WebSocketサポート
// WebSocketServerVerticle.java
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.json.JsonObject;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketServerVerticle extends AbstractVerticle {
private Set<ServerWebSocket> connectedSockets = ConcurrentHashMap.newKeySet();
@Override
public void start(Promise<Void> startPromise) throws Exception {
HttpServer server = vertx.createHttpServer();
// WebSocketハンドラー
server.webSocketHandler(websocket -> {
System.out.println("WebSocket接続: " + websocket.textHandlerID());
connectedSockets.add(websocket);
// 接続通知
JsonObject welcomeMsg = new JsonObject()
.put("type", "welcome")
.put("message", "WebSocketサーバーに接続しました")
.put("clientId", websocket.textHandlerID());
websocket.writeTextMessage(welcomeMsg.encode());
// メッセージ受信ハンドラー
websocket.textMessageHandler(message -> {
try {
JsonObject msgObj = new JsonObject(message);
handleWebSocketMessage(websocket, msgObj);
} catch (Exception e) {
JsonObject errorMsg = new JsonObject()
.put("type", "error")
.put("message", "無効なJSONメッセージ");
websocket.writeTextMessage(errorMsg.encode());
}
});
// 接続終了ハンドラー
websocket.closeHandler(v -> {
System.out.println("WebSocket切断: " + websocket.textHandlerID());
connectedSockets.remove(websocket);
// 他のクライアントに切断を通知
JsonObject disconnectMsg = new JsonObject()
.put("type", "user_disconnected")
.put("clientId", websocket.textHandlerID());
broadcastMessage(disconnectMsg, websocket);
});
// 例外ハンドラー
websocket.exceptionHandler(throwable -> {
System.err.println("WebSocketエラー: " + throwable.getMessage());
connectedSockets.remove(websocket);
});
});
// 定期的なハートビート
vertx.setPeriodic(30000, id -> {
JsonObject heartbeat = new JsonObject()
.put("type", "heartbeat")
.put("timestamp", System.currentTimeMillis())
.put("connectedClients", connectedSockets.size());
broadcastMessage(heartbeat, null);
});
server.listen(8080, result -> {
if (result.succeeded()) {
System.out.println("WebSocketサーバーがポート8080で起動しました");
startPromise.complete();
} else {
startPromise.fail(result.cause());
}
});
}
private void handleWebSocketMessage(ServerWebSocket sender, JsonObject message) {
String type = message.getString("type");
switch (type) {
case "chat":
// チャットメッセージの配信
JsonObject chatMsg = new JsonObject()
.put("type", "chat")
.put("from", sender.textHandlerID())
.put("message", message.getString("message"))
.put("timestamp", System.currentTimeMillis());
broadcastMessage(chatMsg, sender);
break;
case "ping":
// Pingに対するPong応答
JsonObject pongMsg = new JsonObject()
.put("type", "pong")
.put("timestamp", System.currentTimeMillis());
sender.writeTextMessage(pongMsg.encode());
break;
case "get_users":
// 接続中ユーザー一覧の取得
JsonObject usersMsg = new JsonObject()
.put("type", "users_list")
.put("count", connectedSockets.size())
.put("users", connectedSockets.stream()
.map(ServerWebSocket::textHandlerID)
.toArray());
sender.writeTextMessage(usersMsg.encode());
break;
default:
JsonObject unknownMsg = new JsonObject()
.put("type", "error")
.put("message", "不明なメッセージタイプ: " + type);
sender.writeTextMessage(unknownMsg.encode());
}
}
private void broadcastMessage(JsonObject message, ServerWebSocket excludeSocket) {
String messageStr = message.encode();
connectedSockets.forEach(socket -> {
if (socket != excludeSocket && !socket.isClosed()) {
socket.writeTextMessage(messageStr);
}
});
}
}
非同期データベース操作
// DatabaseVerticle.java
package com.example;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.SQLConnection;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
public class DatabaseVerticle extends AbstractVerticle {
private JDBCClient jdbcClient;
@Override
public void start(Promise<Void> startPromise) throws Exception {
// データベース接続設定
JsonObject config = new JsonObject()
.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
.put("driver_class", "org.h2.Driver");
jdbcClient = JDBCClient.createShared(vertx, config);
// テーブル初期化
initializeDatabase().onComplete(dbResult -> {
if (dbResult.succeeded()) {
setupRouter(startPromise);
} else {
startPromise.fail(dbResult.cause());
}
});
}
private Promise<Void> initializeDatabase() {
Promise<Void> promise = Promise.promise();
String createTable = """
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""";
jdbcClient.getConnection(connResult -> {
if (connResult.succeeded()) {
SQLConnection connection = connResult.result();
connection.execute(createTable, createResult -> {
if (createResult.succeeded()) {
// サンプルデータ挿入
String insertData = """
INSERT INTO users (name, email, age) VALUES
('田中太郎', '[email protected]', 30),
('佐藤花子', '[email protected]', 25),
('鈴木一郎', '[email protected]', 35)
""";
connection.execute(insertData, insertResult -> {
connection.close();
if (insertResult.succeeded()) {
promise.complete();
} else {
promise.fail(insertResult.cause());
}
});
} else {
connection.close();
promise.fail(createResult.cause());
}
});
} else {
promise.fail(connResult.cause());
}
});
return promise;
}
private void setupRouter(Promise<Void> startPromise) {
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.get("/api/users").handler(this::getAllUsers);
router.get("/api/users/:id").handler(this::getUser);
router.post("/api/users").handler(this::createUser);
router.put("/api/users/:id").handler(this::updateUser);
router.delete("/api/users/:id").handler(this::deleteUser);
vertx.createHttpServer()
.requestHandler(router)
.listen(8080, result -> {
if (result.succeeded()) {
System.out.println("データベースAPIサーバーがポート8080で起動しました");
startPromise.complete();
} else {
startPromise.fail(result.cause());
}
});
}
private void getAllUsers(RoutingContext context) {
jdbcClient.getConnection(connResult -> {
if (connResult.succeeded()) {
SQLConnection connection = connResult.result();
connection.query("SELECT * FROM users ORDER BY id", queryResult -> {
connection.close();
if (queryResult.succeeded()) {
JsonArray users = new JsonArray();
queryResult.result().getRows().forEach(row -> {
users.add(new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("email", row.getString("email"))
.put("age", row.getInteger("age"))
.put("created_at", row.getString("created_at")));
});
context.response()
.putHeader("content-type", "application/json")
.end(users.encode());
} else {
handleDatabaseError(context, queryResult.cause());
}
});
} else {
handleDatabaseError(context, connResult.cause());
}
});
}
private void getUser(RoutingContext context) {
String idParam = context.request().getParam("id");
try {
int id = Integer.parseInt(idParam);
jdbcClient.getConnection(connResult -> {
if (connResult.succeeded()) {
SQLConnection connection = connResult.result();
connection.queryWithParams(
"SELECT * FROM users WHERE id = ?",
new JsonArray().add(id),
queryResult -> {
connection.close();
if (queryResult.succeeded()) {
if (queryResult.result().getNumRows() > 0) {
JsonObject row = queryResult.result().getRows().get(0);
JsonObject user = new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("email", row.getString("email"))
.put("age", row.getInteger("age"))
.put("created_at", row.getString("created_at"));
context.response()
.putHeader("content-type", "application/json")
.end(user.encode());
} else {
context.response()
.setStatusCode(404)
.putHeader("content-type", "application/json")
.end(new JsonObject()
.put("error", "ユーザーが見つかりません")
.encode());
}
} else {
handleDatabaseError(context, queryResult.cause());
}
});
} else {
handleDatabaseError(context, connResult.cause());
}
});
} catch (NumberFormatException e) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json")
.end(new JsonObject()
.put("error", "無効なユーザーID")
.encode());
}
}
private void createUser(RoutingContext context) {
JsonObject body = context.getBodyAsJson();
if (body == null || !isValidUser(body)) {
context.response()
.setStatusCode(400)
.putHeader("content-type", "application/json")
.end(new JsonObject()
.put("error", "無効なユーザーデータ")
.encode());
return;
}
jdbcClient.getConnection(connResult -> {
if (connResult.succeeded()) {
SQLConnection connection = connResult.result();
connection.updateWithParams(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
new JsonArray()
.add(body.getString("name"))
.add(body.getString("email"))
.add(body.getInteger("age")),
updateResult -> {
connection.close();
if (updateResult.succeeded()) {
JsonObject user = new JsonObject()
.put("id", updateResult.result().getKeys().getInteger(0))
.put("name", body.getString("name"))
.put("email", body.getString("email"))
.put("age", body.getInteger("age"));
context.response()
.setStatusCode(201)
.putHeader("content-type", "application/json")
.end(user.encode());
} else {
handleDatabaseError(context, updateResult.cause());
}
});
} else {
handleDatabaseError(context, connResult.cause());
}
});
}
private void updateUser(RoutingContext context) {
// 実装省略(createUserと同様のパターン)
}
private void deleteUser(RoutingContext context) {
// 実装省略(getUserと同様のパターン)
}
private boolean isValidUser(JsonObject user) {
return user.getString("name") != null && !user.getString("name").trim().isEmpty() &&
user.getString("email") != null && !user.getString("email").trim().isEmpty() &&
user.getInteger("age") != null && user.getInteger("age") > 0;
}
private void handleDatabaseError(RoutingContext context, Throwable error) {
System.err.println("データベースエラー: " + error.getMessage());
context.response()
.setStatusCode(500)
.putHeader("content-type", "application/json")
.end(new JsonObject()
.put("error", "内部サーバーエラー")
.encode());
}
}
Vert.x Unit テスト
// VerticleTest.java
package com.example;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(VertxUnitRunner.class)
public class VerticleTest {
private Vertx vertx;
private HttpClient client;
@Before
public void setUp(TestContext context) {
vertx = Vertx.vertx();
// テストVerticleのデプロイ
Async async = context.async();
vertx.deployVerticle(UserApiVerticle.class.getName(), context.asyncAssertSuccess(id -> {
async.complete();
}));
client = vertx.createHttpClient();
}
@After
public void tearDown(TestContext context) {
vertx.close(context.asyncAssertSuccess());
}
@Test
public void testGetAllUsers(TestContext context) {
Async async = context.async();
client.getNow(8080, "localhost", "/api/users", response -> {
context.assertEquals(200, response.statusCode());
context.assertEquals("application/json; charset=utf-8",
response.getHeader("content-type"));
response.bodyHandler(body -> {
context.assertTrue(body.toString().contains("田中太郎"));
async.complete();
});
});
}
@Test
public void testCreateUser(TestContext context) {
Async async = context.async();
String userData = """
{
"name": "テストユーザー",
"email": "[email protected]",
"age": 28
}
""";
client.post(8080, "localhost", "/api/users")
.putHeader("content-type", "application/json")
.handler(response -> {
context.assertEquals(201, response.statusCode());
response.bodyHandler(body -> {
context.assertTrue(body.toString().contains("テストユーザー"));
async.complete();
});
})
.end(userData);
}
@Test
public void testGetUserNotFound(TestContext context) {
Async async = context.async();
client.getNow(8080, "localhost", "/api/users/999", response -> {
context.assertEquals(404, response.statusCode());
async.complete();
});
}
}