-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for Vert.x expectations as pluggable operators
Issue: #989
- Loading branch information
Showing
3 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Using Vert.x expectations | ||
|
||
Vert.x `Future` support [expectations](https://javadoc.io/static/io.vertx/vertx-core/4.5.10/io/vertx/core/Expectation.html) as predicates on the resolved values. | ||
|
||
A good example of pre-defined expectations are those from `HttpResponseExpectation`: | ||
|
||
```java | ||
Future<JsonObject> future = client | ||
.request(HttpMethod.GET, "some-uri") | ||
.compose(request -> request | ||
.send() | ||
.expecting(HttpResponseExpectation.SC_OK.and(HttpResponseExpectation.JSON)) | ||
.compose(response -> response | ||
.body() | ||
.map(buffer -> buffer.toJsonObject()))); | ||
``` | ||
|
||
In this example the HTTP response is expected to be with status code 200 (`SC_OK`) and have the `application/json` content-type (`JSON`). | ||
|
||
## Where are expectations gone in a Vert.x Mutiny bindings API? | ||
|
||
While expectations are very useful, they apply to `Future` in most of the Vert.x APIs such as the core HTTP client. | ||
|
||
Since the Mutiny bindings generator turns `Future`-returning methods into `Uni`-returning methods, you need to leverage another route to use them. | ||
|
||
## Turning expectations into operators | ||
|
||
The `io.smallrye.mutiny.vertx.core.Expectations` class that comes with the `smallrye-mutiny-vertx-core` artifact bring 2 helper methods. | ||
|
||
The `expecting` methods build functions that can be used with the `Uni::plug` operator, as in: | ||
|
||
```java | ||
Expectation<Integer> tenToTwenty = (value -> value >= 10 && value <= 20); | ||
|
||
return Uni.createFrom().item(15) | ||
.plug(expectation(tenToTwenty)); | ||
``` | ||
|
||
Some expectations such as those in `HttpResponseExpectation` apply to types from the core Vert.x APIs. | ||
Since the Mutiny bindings generate shims (e.g., `io.vertx.mutiny.core.http.HttpResponseHead`), you need to extract the delegate type for the expectations to work on the correct type (e.g., `io.vertx.core.http.HttpResponseHead`): | ||
|
||
```java | ||
return vertx.createHttpClient() | ||
.request(HttpMethod.GET, port, "localhost", "/") | ||
.chain(HttpClientRequest::send) | ||
.plug(expectation(HttpClientResponse::getDelegate, status(200).and(contentType("text/plain")))) | ||
.onItem().transformToUni(HttpClientResponse::body) | ||
``` | ||
|
||
The extractor function here is `HttpClientResponse::getDelegate`, so that the `status(200).and(contentType("text/plain"))` expectation applies to `io.vertx.core.http.HttpResponseHead` and not the `io.vertx.mutiny.core.http.HttpResponseHead` generated shim. |
61 changes: 61 additions & 0 deletions
61
...y-clients/vertx-mutiny-core/src/main/java/io/smallrye/mutiny/vertx/core/Expectations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package io.smallrye.mutiny.vertx.core; | ||
|
||
import java.util.function.Function; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.core.Expectation; | ||
|
||
/** | ||
* Helper methods to turn Vert.x {@link Expectation} that work on {@link io.vertx.core.Future} into {@link Uni} that | ||
* can be used in a pipeline using the {@link Uni#plug(Function)} operator, as in: | ||
* | ||
* <pre>{@code | ||
* vertx.createHttpClient() | ||
* .request(HttpMethod.GET, port, "localhost", "/") | ||
* .chain(HttpClientRequest::send) | ||
* .plug(expectation(HttpClientResponse::getDelegate, status(200).and(contentType("text/plain")))) | ||
* .onItem().transformToUni(HttpClientResponse::body) | ||
* }</pre> | ||
*/ | ||
public interface Expectations { | ||
|
||
/** | ||
* Yields a function to turn an {@link Expectation} into a {@link Uni}. | ||
* | ||
* @param expectation the expectation | ||
* @return the mapping function | ||
* @param <T> the element type | ||
*/ | ||
static <T> Function<Uni<T>, Uni<T>> expectation(Expectation<? super T> expectation) { | ||
return uni -> uni.onItem().transformToUni(item -> { | ||
if (expectation.test(item)) { | ||
return Uni.createFrom().item(item); | ||
} else { | ||
return Uni.createFrom().failure(expectation.describe(item)); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Yields a function to turn an {@link Expectation} into a {@link Uni} and uses an extractor so that expectations | ||
* work on the correct types (e.g., {@link io.vertx.core.http.HttpResponseHead}) instead of the Mutiny shim types | ||
* (e.g., {@link io.vertx.mutiny.core.http.HttpResponseHead}. | ||
* | ||
* @param extractor the extractor function, often a reference to a {@code getDelegate()} method | ||
* @param expectation the expectation | ||
* @return the mapping function | ||
* @param <T> the element type | ||
* @param <R> the extracted element type | ||
*/ | ||
static <T, R> Function<Uni<T>, Uni<T>> expectation(Function<T, R> extractor, Expectation<? super R> expectation) { | ||
return uni -> uni | ||
.onItem().transformToUni(item -> { | ||
R unwrapped = extractor.apply(item); | ||
if (expectation.test(unwrapped)) { | ||
return Uni.createFrom().item(item); | ||
} else { | ||
return Uni.createFrom().failure(expectation.describe(unwrapped)); | ||
} | ||
}); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
...ients/vertx-mutiny-core/src/test/java/io/smallrye/mutiny/vertx/core/ExpectationsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package io.smallrye.mutiny.vertx.core; | ||
|
||
import static io.smallrye.mutiny.vertx.core.Expectations.expectation; | ||
import static io.vertx.core.http.HttpResponseExpectation.contentType; | ||
import static io.vertx.core.http.HttpResponseExpectation.status; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.time.Duration; | ||
|
||
import org.junit.Test; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; | ||
import io.vertx.core.Expectation; | ||
import io.vertx.core.VertxException; | ||
import io.vertx.core.http.HttpMethod; | ||
import io.vertx.mutiny.core.Vertx; | ||
import io.vertx.mutiny.core.buffer.Buffer; | ||
import io.vertx.mutiny.core.http.HttpClientRequest; | ||
import io.vertx.mutiny.core.http.HttpClientResponse; | ||
import io.vertx.mutiny.core.http.HttpServer; | ||
|
||
public class ExpectationsTest { | ||
|
||
@Test | ||
public void plugMatchingExpectation() { | ||
Expectation<Integer> tenToTwenty = (value -> value >= 10 && value <= 20); | ||
|
||
UniAssertSubscriber<Integer> sub = Uni.createFrom().item(15) | ||
.plug(expectation(tenToTwenty)) | ||
.subscribe().withSubscriber(UniAssertSubscriber.create()); | ||
sub.assertItem(15); | ||
} | ||
|
||
@Test | ||
public void plugFailingExpectation() { | ||
Expectation<Integer> tenToTwenty = (value -> value >= 10 && value <= 20); | ||
|
||
UniAssertSubscriber<Integer> sub = Uni.createFrom().item(42) | ||
.plug(expectation(tenToTwenty)) | ||
.subscribe().withSubscriber(UniAssertSubscriber.create()); | ||
sub.assertFailedWith(VertxException.class, "Unexpected result: 42"); | ||
} | ||
|
||
@Test | ||
public void httpAssertion() { | ||
Vertx vertx = Vertx.vertx(); | ||
try { | ||
|
||
HttpServer server = vertx.createHttpServer() | ||
.requestHandler(req -> req.response() | ||
.setStatusCode(200) | ||
.putHeader("content-type", "text/plain") | ||
.endAndForget("Yolo")) | ||
.listen() | ||
.await().atMost(Duration.ofSeconds(30)); | ||
|
||
int port = server.actualPort(); | ||
Buffer payload = vertx.createHttpClient() | ||
.request(HttpMethod.GET, port, "localhost", "/") | ||
.chain(HttpClientRequest::send) | ||
.plug(expectation(HttpClientResponse::getDelegate, status(200).and(contentType("text/plain")))) | ||
.onItem().transformToUni(HttpClientResponse::body) | ||
.await().atMost(Duration.ofSeconds(5)); | ||
assertThat(payload.toString(StandardCharsets.UTF_8)).isEqualTo("Yolo"); | ||
|
||
} finally { | ||
vertx.closeAndAwait(); | ||
} | ||
} | ||
} |