Testing service with Vert.x 4 Web-Client

I tried to write tests with Vert.x 3 Web-Client. It is callback hell. You can read this post.

Now Vert.x 4 has been released. And it contains methods that return Future. Future can be converted to CompletableFuture. So I can block thread with method get(). It is what I need for my tests.

My code now looks like this

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;

class AccountTest {

    private static String token;
    private static WebClient webClient;

    @BeforeAll
    static void beforeAll() throws Throwable {
        Vertx vertx = Vertx.vertx();
        webClient = WebClient.create(vertx);
        login();
    }

    @Test
    public void createAccountTest() throws Exception {
        JsonObject account = new JsonObject();
        account.put("name", "accountLOSS1");
        account.put("type", "LOSS");
        account.put("summ", 1000.00);
        Future<HttpResponse<Buffer>> responseFuture = webClient
        	.post(8080, "localhost", "/api/accounts")
	        .putHeader("Authorization", "Bearer " + token)
	        .sendJsonObject(account);

        HttpResponse<Buffer> response = succeeding(responseFuture);
        assertEquals(response.getHeader("content-type"), "application/json");
        long accountId = Long.parseLong(response.bodyAsString());
        assertTrue(accountId > 0, "No account Id");
    }

    private static void login() throws Exception {
        JsonObject user = new JsonObject();
        user.put("login", "testLogin");
        user.put("password", "testPassword");
        Future<HttpResponse<Buffer>> future = webClient
        		.post(8080, "localhost", "/api/login")
        		.sendJsonObject(user);
        HttpResponse<Buffer> response = succeeding(future);
        token = response.getHeader("X-Auth-Token");
        JsonObject userObject = response.bodyAsJsonObject();
        assertEquals(login, userObject.getString("name"));
    }
    
    private static HttpResponse<Buffer> succeeding(Future<HttpResponse<Buffer>> ar)
    		throws Exception {
    	CompletionStage<HttpResponse<Buffer>> completionStage = ar.toCompletionStage();
        CompletableFuture<HttpResponse<Buffer>> completableFuture = completionStage.toCompletableFuture();
        HttpResponse<Buffer> response = completableFuture.get(10, TimeUnit.SECONDS);
        if (response.statusCode() != 200) {
            String errors = response.bodyAsString();
            Exception exception = new Exception("Wrong http status code: "
                    + response.statusCode() + " " + errors);
            throw exception;
        }
        return response;
    }
}

Looks good. Looks sync!

I can create a batch of accounts asynchronously too. With Future I don't need VertxTestContext and checkpoints. CompletableFuture.allOf waits for all requests.

@Test
public void createAccounts() throws Exception {
    List<JsonObject> accounts = new ArrayList<>();

    Future<HttpResponse<Buffer>> futureOwn1;
    {
        JsonObject account = new JsonObject();
        account.put("name", "accountOWN1");
        account.put("type", "OWN");
        account.put("summ", 1000.00);
        futureOwn1 = webClient.post(8080, "localhost", "/api/accounts")
                .putHeader("Authorization", "Bearer " + token)
                .sendJsonObject(account);
        futureOwn1.compose(response -> {
            if (response.statusCode() != 200) {
                String errors = response.bodyAsString();
                return Future.failedFuture(errors);
            }
            assertEquals(response.getHeader("content-type"), "application/json");
            long accountId = Long.parseLong(response.bodyAsString());
            account.put("id", accountId);
            return Future.succeededFuture(account);
        });
        accounts.add(account);
    }

    Future<HttpResponse<Buffer>> futureOwn2;
    {
        JsonObject account = new JsonObject();
        account.put("name", "accountOWN2");
        account.put("type", "OWN");
        account.put("summ", 1000.00);
        futureOwn2 = webClient.post(8080, "localhost", "/api/accounts")
                .putHeader("Authorization", "Bearer " + token)
                .sendJsonObject(account);
        futureOwn2.compose(response -> {
                if (response.statusCode() != 200) {
                    String errors = response.bodyAsString();
                    return Future.failedFuture(errors);
                }
                assertEquals(response.getHeader("content-type"), "application/json");
                long accountId = Long.parseLong(response.bodyAsString());
                account.put("id", accountId);
                return Future.succeededFuture(account);
        });
        accounts.add(account);
    }
    CompletableFuture.allOf(
        futureOwn1.toCompletionStage().toCompletableFuture(),
        futureOwn2.toCompletionStage().toCompletableFuture(),
        ).get(10, TimeUnit.SECONDS);
    
    List<JsonObject> fromDb = getAccounts();
    for (JsonObject account : accounts) {
        boolean found = fromDb.stream()
                .anyMatch(db -> db.getLong("id").equals(account.getLong("id")));
        Assertions.assertTrue(found, "Not found in DB " + account);
    }
}

Conclusion

With Vert.x 4 I can make my code more clean and readable.