I have a method which returns Mono. So I try to user step verifier to do a integrate test.
public Mono<Void> process(InputBody body) {
retrun webClient
.post()
.uri("/submit")
.body(Mono.just(body), InputBody .class)
.retrieve()
.bodyToMono(Void.class)
.subscribe()
}
I want to do a integrate test for this.
my question is, when we use Web Client Test how to return mono void and step verify it?
webClient
.post()
.uri("/submit")
.body(Mono.just(body), InputBody .class)
.exchange()
.........
how to complete things? I tried but failed.
Related
#Controller
public class IndexController {
#Resource
WebClient webClient;
#PostMapping
public Mono<User> index(#Valid #RequestBody Mono<User> user){
// I'm using webclient to call another service, just pass the mono object like this
return this.getResponse(user);
}
private Mono<User> getResponse(Mono<User> user) {
return webClient.post()
.body(user,User.class)
.retrieve().bodyToMono(User.class);
}
}
In above scenario, I don't need to operate the request body. request body will not be validated.
How can I make webflux to validate the request body?
In that scenario, SpringMVC(not sure which componment will do) connot vaildate the Mono, so I cannot get a webexchangebindexception. the validation will occur when the Mono is subscribed which is also when webclient actually send the message.
before webclient actually sending message, validation will be done,webexchangebindexception will be catch by webclient. webclient will wrap this exception into webclientrequestexception by default. we can add our own exception handler to webclient so that we can just throw webexchangebindexception to our global exception handler.
So I've created a custom filter that, when accessed, will create a webflux client and post to a predetermined url. This seems to work fine when running, but when testing this code the test is hanging (until I cancel the test). So I feel there is a possible memory leak on top of not being able to complete the test to make sure this route is working properly. If I switch the WebClient method to get() then a resulting test of the filter works fine. Something with a post() I am not sure what is missing.
#Component
class ProxyGatewayFilterFactory: AbstractGatewayFilterFactory<ProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
GatewayFilter { exchange, chain ->
exchange.request.mutate().header("test","test1").build()
WebClient.create().post()
.uri(params.proxyBasePath)
.body(BodyInserters.fromDataBuffers(exchange.request.body))
.headers { it.addAll(exchange.request.headers) }
.exchange()
.flatMap {
println("the POST statusCode is "+it.statusCode())
Mono.just(it.statusCode().is2xxSuccessful)
}
.map {
exchange.request.mutate().header("test", "test2").build()
println("exchange request uri is " + exchange.request.uri)
println("exchange response statusCode is "+ exchange.response.statusCode)
exchange
}
.flatMap(chain::filter)
}, params.order)
}
Taken from the documentation, if using exchange you have an obligation to consume the body.
Unlike retrieve(), when using exchange(), it is the responsibility of the application to consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak. The Javadoc for ClientResponse lists all the available options for consuming the body. Generally prefer using retrieve() unless you have a good reason for using exchange() which does allow to check the response status and headers before deciding how to or if to consume the response.
Spring framework 5.2.9 Webclient
This api has been changed in the latest version of the spring framework 5.3.0 now spring will force you to consume the body, because developers didn't actually read the docs.
I tried to add a quarkus-rest-client sample for my post-service which is a simple REST API built with Quarkus.
The java version is working well.
When I added another Kotlin to test the kotlin and Gradle support in Quarkus, it failed, the REST Client interface can not be injected as CDI bean.
The PostControlloer is Jaxrs resource to expose an aggregated APIs that combined the original two APIs.
#Path("/api")
#RequestScoped
class PostController(#Inject #RestClient val client: PostResourceClient) {
// #Inject
// #RestClient
// lateinit var client: PostServiceClient
#GET
#Produces(MediaType.APPLICATION_JSON)
fun getPosts(#QueryParam("q")
q: String,
#QueryParam("offset")
#DefaultValue("0")
offset: Int,
#QueryParam("limit")
#DefaultValue("10")
limit: Int): Response {
val posts = this.client.getAllPosts(q, offset, limit).entity as List<Post>
val count = this.client.countAllPosts(q).entity as Long
return ok(PostPage(posts, count)).build()
}
}
The above two approaches to inject a Bean are failed.
The REST Client interface:
#Path("/posts")
#RegisterRestClient
interface PostResourceClient {
#GET
#Produces(MediaType.APPLICATION_JSON)
fun getAllPosts(
#QueryParam("q")
q: String,
#QueryParam("offset")
#DefaultValue("0")
offset: Int,
#QueryParam("limit")
#DefaultValue("10")
limit: Int
): Response
#GET
#Path("count")
#Produces(MediaType.APPLICATION_JSON)
fun countAllPosts(
#QueryParam("q")
q: String
): Response
}
The application config for this Rest Client interface.
com.example.PostResourceClient/mp-rest/url=http://localhost:8080
com.example.PostResourceClient/mp-rest/scope=javax.inject.Singleton
The complete codes is here.
Duplicated with Error to inject some dependency with kotlin + quarkus it is a MicroProfile RestClient issue. See the workaround in the original SO answer:
#Inject
#field: RestClient
lateinit internal var countriesService: CountriesService
An issue is already openned on MicroProfile RestClient to have a fix for this and tracked on the Quarkus issue traker: https://github.com/quarkusio/quarkus/issues/5413
Scenario:
Open app without internet, the app will try to do a request, and will fail
Turn on internet connection, and press retry button to trigger internet request
Retrofit & okhttp will always give me HTTP FAILED: java.net.SocketTimeoutException: timeout
Restarting the app with internet enabled from start will make everything work, unless I close it again, and fail a request, from that point on it will give me the same error.
I never had this issue on Java, just on Kotlin.
private val interceptor: Interceptor =
object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var builder = chain.request().newBuilder()
Prefs.token?.let { token ->
builder = builder.addHeader("Authorization", "Bearer $token")
}
return chain.proceed(builder.build())
}
}
private val httpLoggingInterceptor: HttpLoggingInterceptor by lazy {
val interceptor = HttpLoggingInterceptor()
interceptor.level =
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
interceptor
}
private val httpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(interceptor)
.build()
}
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://api.secret.com/v1/")
.addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build()
}
And the service classes look like this
#GET("something")
fun something(): Call<SomeResponse>
I've tried playing around with timeout values, no matter the timeout time, I will get the same error.
Creating a new http client for every request will fix the issue, but I don't think is a good idea.
Your issue looks like OkHttp Bug. If you follow the link, you will find long discussion with many possible solutions.
Following solution works for my project:
Update OkHttp at lest up to 4.3.0.
Set ping interval, for example 1 second
okHttpClientBuilder.pingInterval(1, TimeUnit.SECONDS)
How it works
The root of the issue is that Android OS doesn't provide any way to know that connection isn't active any more. So that for library connection looks like alive, but it's already dead. As a result we get timeout exception on every request. Once we set ping, OkHttp starts sending Ping frames, so that if server doesn't respond library knows that connection is already dead, and it's time to create a new one.
Not recommended solutions, but it should work
Turn-off connection pool
okHttpClientBuilder.connectionPool(new ConnectionPool(0, 1, TimeUnit.NANOSECONDS))
Use Http 1.1
okHttpClientBuilder.protocols(listOf(Protocol.HTTP_1_1))
In both not recommended solutions you just stop reusing already opened connection that makes each request time little bit longer.
Is pact consumer test for generating contract json files?
I am studing pact and got qurioused about what is the consumer test for? It tests the response that the test class defindes.
In my code below. I defined a response with 200 and simple body, then Test it calling by mockProvider. seems useless. Anybody please give me some guides.
public class PactTest {
#Rule
public PactProviderRuleMk2 mockProvider
= new PactProviderRuleMk2("test-provider", "localhost", 8017, this);
#Pact(consumer = "test-consumer")
public RequestResponsePact createPact(PactDslWithProvider builder){
Map<String, String> headers = new HashMap<>();
return builder
.given("test Get")
.uponReceiving("GET REQUEST")
.path("/pact")
.method("GET")
.willRespondWith()
.status(200)
.headers(headers)
.body("{\"condition\": true, \"name\":\"tom\"}")
.toPact();
}
#Test
#PactVerification
public void givenGet_whenSendRequest_shouldReturn200withProperHeaderAndBody() {
ResponseEntity<String> res = new RestTemplate()
.getForEntity(mockProvider.getUrl()+"/pact", String.class);
assertThat(res.getStatusCode().value()).isEqualTo(200);
}
}
Short answer - no.
Calling the mock API in the test independent of your actual consumer code is worthless (as you imply), because it is a self-fulfilling prophecy. Pact is designed to test the collaborating service on the Consumer side; the adapter code that makes the call to the Provider.
Typically, this call will pass through things like data-access layers and other intermediates. Your Pact tests would use a service that uses these, and the benefit is that the contract gets defined through this process, that is guaranteed to be up-to-date with consumer needs, because it is generated via your code.
We've just updated the docs today, perhaps that helps.