New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add WebTestClient
and TestHttpResponse
to enable more fluent testing of HTTP responses
#4740
base: main
Are you sure you want to change the base?
Conversation
WebTestClient
and TestHttpResponse
WebTestClient
and TestHttpResponse
to enable more fluent testing of HTTP responses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have time to fully review this PR. I will see in detail soon.
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/HttpDataAssert.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/TestHttpResponse.java
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClientUtil.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/DefaultWebTestClient.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/AssertThat.java
Outdated
Show resolved
Hide resolved
e69f7b7
to
948df0e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late review.
The overall direction looks good although we need minor changes and polish Javadoc.
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClientUtil.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/TestHttpResponse.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/AssertThat.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/TestHttpResponse.java
Show resolved
Hide resolved
I suggest you apply the As you know, we usually use AssertJ. Maybe in the follow-up PR, we can add
|
(I'm sorry, but I couldn't leave a reply due to a bug on GitHub, so I left a comment instead.) I completely agree with your opinion. I will contribute as a follow-up to this PR. 😄 👍 In fact, initially I wrote the code using AssertJ according to the developer guide, but due to dependency issues, I ended up using the JUnit API. I have a question at this point. (related #4740 (comment)) |
1. Remove RESPONSE_STREAMING_REQUEST_OPTIONS static variable. 2. Remove unused factory methods. 3. Replace checkState method with junit5 Assertions API
override requestAutoAbortDelay, requestAutoAbortDelayMillis
0cfeeac
to
7401e66
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newly added exception test API is as follows.
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/AbortedTestHttpResponse.java
Outdated
Show resolved
Hide resolved
junit5/src/test/java/com/linecorp/armeria/testing/junit5/client/WebTestClientTest.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/AbortedTestHttpResponse.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/DefaultTestHttpResponse.java
Outdated
Show resolved
Hide resolved
if (this == obj) { | ||
return true; | ||
} | ||
|
||
if (!(obj instanceof TestHttpResponse)) { | ||
return false; | ||
} | ||
|
||
final TestHttpResponse that = (TestHttpResponse) obj; | ||
|
||
return informationals().equals(that.informationals()) && | ||
headers().equals(that.headers()) && | ||
content().equals(that.content()) && | ||
trailers().equals(that.trailers()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (this == obj) { | |
return true; | |
} | |
if (!(obj instanceof TestHttpResponse)) { | |
return false; | |
} | |
final TestHttpResponse that = (TestHttpResponse) obj; | |
return informationals().equals(that.informationals()) && | |
headers().equals(that.headers()) && | |
content().equals(that.content()) && | |
trailers().equals(that.trailers()); | |
if (this == obj) { | |
return true; | |
} | |
if (!(obj instanceof TestHttpResponse)) { | |
return false; | |
} | |
final TestHttpResponse that = (TestHttpResponse) obj; | |
return delegate.equals(that.unwrap()); |
Before using delegate.equals()
, I first cast obj
to TestHttpResponse
, and then unwrap()
it before using it as an argument. I made this change because when using obj
without casting it to TestHttpResponse
, the result was always false if the runtime type of obj
was TestHttpResponse
.
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/DefaultTestHttpResponse.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/HttpHeadersAssert.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/ThrowableAssert.java
Show resolved
Hide resolved
This PR seems ready to review by other maintainers. Let's get some opinions about the current direction. PTAL |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4740 +/- ##
============================================
- Coverage 73.95% 73.82% -0.14%
- Complexity 20115 20917 +802
============================================
Files 1730 1819 +89
Lines 74161 77163 +3002
Branches 9465 9794 +329
============================================
+ Hits 54847 56963 +2116
- Misses 14837 15569 +732
- Partials 4477 4631 +154 ☔ View full report in Codecov by Sentry. |
… content)` override.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow that's a lot of code 😅 Looks good overall 👍
I'm wondering if it's worth just creating an assertj
module at this point. What do you think others?
Nevermind this comment 😅 Talked with the team and we decided not to move this to a separate module for this iteration
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClient.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClient.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/HttpStatusAssert.java
Show resolved
Hide resolved
...rc/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClientRequestPreparation.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/WebTestClientBuilder.java
Outdated
Show resolved
Hide resolved
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/HttpDataAssert.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, left some minor comments
TestHttpResponse response() { | ||
return response; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of adding a base assertion which accepts a vararg of consumers?
for (val c: consumers) {
assertAll(() -> consumer.accept(actual()));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggestion. I'm sorry, but I can't understand the intent behind the suggestion. Could you please clarify in which situations this code is being used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of something like the following so that users could fall back in case they can't find an assertion that is sufficient for their use-case.
@SafeVarargs
public final TestHttpResponse satisfies(ThrowableConsumer<T>... consumers) {
for (ThrowableConsumer<T> consumer: consumers) {
assertAll(() -> consumer.accept(actual));
}
return response();
}
junit5/src/main/java/com/linecorp/armeria/testing/junit5/client/HttpDataAssert.java
Show resolved
Hide resolved
/** | ||
* Asserts that the {@link String} representation of actual {@link HttpData} contains the given value. | ||
*/ | ||
public TestHttpResponse stringContains(Charset charset, String expected) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind also formatting the other methods so that the verb comes first? (following how junit, assertj are naming assertions)
public TestHttpResponse stringContains(Charset charset, String expected) { | |
public TestHttpResponse containsString(Charset charset, String expected) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! When I named the stringContains
method, my intention was not to convey "the HTTP content contains the parameter string" but rather "when representing HTTP content as a string, it contains the provided string as an argument." If I were to name it more explicitly, it might be something like toStringAndItWillContain
. The background behind choosing such names was due to the ability to represent HTTP content in various ways, such as UTF-8, ASCII, and others. Initially, I thought of stringUtf8Contains
, and then I came up with stringContains
.
As you mentioned, it does seem awkward. How about remove the stringUtf8Contains
and stringAsciiContains
methods and simply use shorter method namescontains(String)
and contains(Charset charset, String expected)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my intention was not to convey "the HTTP content contains the parameter string" but rather "when representing HTTP content as a string, it contains the provided string as an argument."
I see, ideally I think we would as a StringAssert
class then and chain like the following:
assertContent().asString().isEqualTo(...);
I think this can be done separately though - what do you think of focusing on byte equality since HttpData
is essentially a byte array?
Users probably still find this useful - i.e.
assertContent().isEqualTo("".getBytes());
…tatus range assertion method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry it took a while - did another round. I think this PR is almost done, let me know if anything doesn't make sense 🙇
* Use the factory methods in {@link TestBlockingWebClient} if you do not have many options to override. | ||
* Please refer to {@link ClientBuilder} for how decorators and HTTP headers are configured | ||
*/ | ||
public final class TestBlockingWebClientBuilder extends AbstractWebClientBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) I'm not sure how useful this class is given that we can always use TestBlockingWebClient#of(WebClient)
.
What do you think of removing this class for now and adding it later if there is demand for such a fluent builder?
* } | ||
* }</pre> | ||
*/ | ||
public interface TestBlockingWebClient extends ClientBuilderParams, Unwrappable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of modifying s.t. TestBlockingWebClient
extends BlockingWebClient
instead?
I think we could achieve this by:
- Rename
BlockingWebClientRequestPreparation
toDefaultBlockingWebClientRequestPreparation
and makeBlockingWebClientRequestPreparation
an interface - Make
TestBlockingWebClient
extendBlockingWebClient
- Make
TestHttpResponse
extendAggregatedHttpResponse
We should be able to maintain these APIs more easily this way
@@ -370,6 +371,24 @@ public RestClient restClient(Consumer<WebClientBuilder> webClientCustomizer) { | |||
return delegate.restClient(webClientCustomizer); | |||
} | |||
|
|||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Instead of creating new methods, could we just return TestBlockingWebClient
from ServerExtension#blockingWebClient
directly?
TestHttpResponse response() { | ||
return response; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of something like the following so that users could fall back in case they can't find an assertion that is sufficient for their use-case.
@SafeVarargs
public final TestHttpResponse satisfies(ThrowableConsumer<T>... consumers) {
for (ThrowableConsumer<T> consumer: consumers) {
assertAll(() -> consumer.accept(actual));
}
return response();
}
/** | ||
* Asserts that the {@link String} representation of actual {@link HttpData} contains the given value. | ||
*/ | ||
public TestHttpResponse stringContains(Charset charset, String expected) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my intention was not to convey "the HTTP content contains the parameter string" but rather "when representing HTTP content as a string, it contains the provided string as an argument."
I see, ideally I think we would as a StringAssert
class then and chain like the following:
assertContent().asString().isEqualTo(...);
I think this can be done separately though - what do you think of focusing on byte equality since HttpData
is essentially a byte array?
Users probably still find this useful - i.e.
assertContent().isEqualTo("".getBytes());
* Asserts that the actual {@link HttpHeaders} contains the given name and {@link String} value. | ||
* The {@code name} and the {@code value} cannot be null. | ||
*/ | ||
public TestHttpResponse contains(CharSequence name, String value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: What do you think of
- Removing the other
contains*
variants since they don't seem to be providing much value - the object is string-ified internally anyways - Accepting
Object
for the general case
public TestHttpResponse contains(CharSequence name, String value) { | |
public TestHttpResponse contains(CharSequence name, Object value) { |
private final TestHttpResponse response; | ||
|
||
AbstractResponseAssert(T actual, TestHttpResponse response) { | ||
requireNonNull(actual, "actual"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Although I think it may be useful to let users check nullability via assertions, I don't think it's necessary at this stage. I'd like to focus on getting this PR merged first.
* Asserts that the message of the actual {@link Throwable} starts with the given message. | ||
* The {@code message} cannot be null. | ||
*/ | ||
public ThrowableAssert hasMessageStartingWith(String message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of removing hasMessageStartingWith|hasMessageNotContaining|hasMessageEndingWith
assertions?
I imagine adding a StringAssert
later on
e.g.
assertCause().message().startsWith()
} | ||
|
||
@Override | ||
public ThrowableAssert assertCause() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matching the class name
public ThrowableAssert assertCause() { | |
public ThrowableAssert assertThrowable() { |
private final Throwable cause; | ||
|
||
ThrowableAssert(Throwable cause) { | ||
requireNonNull(cause, "cause"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I think the nullibility of this class can be relaxed, but I'd rather we not do this in this already large PR
Motivation:
WebTestClient
#4339Modifications:
TestHttpResponse
, which is similar toAggregatedHttpResponse
, and includes additional assertion methods.TestHttpResponse
,assertStatus
, allows you to assert the status of the response and returns theTestHttpResponse
object, enabling you to test other aspects of the response more fluently.WebTestClient
that returnsTestHttpResponse
by defaultResults:
WebTestClient
#4339WebClient
versus testing it usingWebTestClient
.