How to get a Flux from a Mono flatmap? - spring

I have the following code:
public Flux<Foo> getFoos(String xyz) {
return getBar(xyz).flatMap(b -> Flux.empty()));
}
But it results in a compilation error because getBars() returns a Mono<Bar> instead of a Flux<Bar>. How can I return a Flux from a flatMap() of a Mono value? Thanks.

Found the solution. I just had to use flatMapMany() instead of flatMap()

Related

Unable to return Mono<Compliance>

I'm trying to retrieve Mono from DB and then filter the Compliance List which is inside the PortCall object based on one condition and finally return a Compliance or Mono
Below is my Mongo DB query
#Query("{vesselCode : ?0, arrivalVoyageCode: ?1}")
Mono<PortCall> findDeadlineTimestamp(String vesselCode, String arrivalVoyageCode);
Below is the usage in ServiceImpl to retrieve Mono
Mono<Compliance> cmp = portCallRepository.findDeadlineTimestamp(arrivalVoyageCode, vesselCode)
.doOnNext(p->p.getCompliance().stream()
.filter(c->c.getId().equalsIgnoreCase(compId))).subscribe();
You should use Reactor's operators instead of Java 8 streams.
The expected way to do that is to actually use the map operator along with the filter:
Mono<Compliance> getCompliance() {
return portCallRepository.findDeadlineTimestamp(arrivalVoyageCode, vesselCode)
.map(e -> e.getCompliance())
.filter(c -> c.getId().equalsIgnoreCase(compId));
}
Then, caller will subscribe:
getCompliance().subscribe()

Issue with Flux

Cannot convert from Mono to Flux
I'm getting this error while trying below code
public Flux<PortCall> updateByFindById(String gsisKey, PortCall portCall) {
return portCallRepository.findAllByVesselCodeOrderBySequenceNo(portCall.getVesselCode())
.switchIfEmpty(
Mono.error(new DataNotFoundException(HttpStatus.NO_CONTENT, PortCallConstants.ROUTE_NOT_FOUND)))
.collectList().flatMap(list -> {
if (list.size() > 0)
return Mono.just(list)
.switchIfEmpty(Mono.empty());
});
}
Please suggest a workaround
Using collectList() and check for an empty list is redundant, because if your repository method doesn't emit any elements, it automatically calls switchIfEmptyoperator.
Just leave it like that:
return portCallRepository.findAllByVesselCodeOrderBySequenceNo(portCall.getVesselCode())
.switchIfEmpty(
Mono.error(new DataNotFoundException(HttpStatus.NO_CONTENT, PortCallConstants.ROUTE_NOT_FOUND)));

Updating Mono object by another Mono object

Dears,
I'm a stuck with implementing a function (it is basically an update operation) that's capable of taking a Mono as a param and return an updated version of Mono where:
the returned instance derives from a db query;
the updated version of Mono contains fields picked by Mono.
This is the sample code (that works from providing directly the object, without using the Mono instance:
public Mono<CompanyDto> updateById(String id, CompanyDto companyDtoMono) {
return getCompanyById(id).map(companyEntity -> {
companyEntity.setDescription(companyDtoMono.getDescription());
companyEntity.setName(companyDtoMono.getName());
return companyEntity;
}).flatMap(companyEntity2 -> reactiveNeo4JTemplate.save(companyEntity2)).map(companyEntity -> companyMapper.toDto(companyEntity));
}`
Question is: how can I change the code if the function signature would be
public Mono<CompanyDto> updateById(String id, Mono<CompanyDto> companyDtoMono)
PS:
getCompanyById(id)
returns a
Mono<CompanyEntity>
Thanks,
best
FB
There are many solutions for this problem but one of them is using Zip
public Mono<CompanyDto> updateById(String id, Mono<CompanyDto> companyDtoMono){
return Mono.zip(getCompanyById(id),companyDtoMono,(companyEntity, companyDto) -> {
companyEntity.setDescription(companyDto.getDescription());
companyEntity.setName(companyDto.getName());
return companyEntity;
})
.flatMap(companyEntity2 -> reactiveNeo4JTemplate.save(companyEntity2))
.map(companyEntity -> companyMapper.toDto(companyEntity));
}

How to mock webclient in Kotlin and spring boot for unit tests with mockk framework?

I have the following piece of code in Kotlin (using WebFlux), which I wanna test:
fun checkUser(user: People.User?): Mono<Unit> =
if (user==null) {
Mono.empty()
} else {
webClient.get().uri {
uriBuilder -> uriBuilder
//... building a URI
}.retrieve().bodyToMono(UserValidationResponse::class.java)
.doOnError {
//log something
}.map {
if (!item.isUserValid()) {
throw InvalidUserException()
}
}
}
My unit test so far looks like this:
#Test
fun `Returns error when user is invalid`() {
val user = People.User("name", "lastname", "street", "zip code")
//when
StepVerifier.create(checkUser(user))
//then
.expectError(InvalidUserException::class.java)
.verify()
}
However when I run it, it throw the following error:
io.mockk.MockKException: no answer found for: WebClient(#1).get()
at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:20)
I guess the error occurs because I havent mocked WebClient(#1).get() but I am not sure how to mock it. So far I have tried:
every { webClient.get() } returns WebClient.RequestHeadersUriSpec
but it doesnt compile. The error says:
Classifier 'RequestHeadersUriSpec' does not have a companion object, and thus must be initialized here
Someone knows how I can mock WebClient(#1).get()? Thanks in advance
Basically you need something like this:
mock ResponseSpec - mock the body or error in whichever way you need for the respective test case
mock RequestHeadersUriSpec - let the retrieve() method return the ResponseSpec mock
mock WebClient - let the get() method return the RequestHeadersUriSpec mock
Here is a full example:
val response = mockk<WebClient.ResponseSpec>()
val spec = mockk<WebClient.RequestHeadersUriSpec<*>>()
val client = mockk<WebClient>()
every { response.bodyToMono(String::class.java) } returns Mono.just("Hello StackOverflow")
every { spec.retrieve() } returns response
every { client.get() } returns spec
println(client.get().retrieve().bodyToMono(String::class.java).block())
This will correctly print the Hello StackOverflow string.
Though it may be a "historical" question, I actually also had this problem recently.
Just as what Krause mentioned, the full call path of WebClient should be mocked. This means the method stream in every{} block should as the same as WebClient call. In your case, it may be something like
every{webClient.get().uri {???}.retrieve().bodyToMono(???)} returns Mono.just(...)
The next question is something about the error message io.mockk.MockKException: no answer found for: RequestBodyUriSpec(#3).uri(......). The key to the question is methods with parameters and without parameters are totally different things.
Thus, for target method, a uri(Function<UriBuilder, URI> uriFunction) is called(a lambda expression is used here to instead of Function interface). However, for mock method, a uri() method without any parameter is called. This is why the error message said , "no answer found for ...". Therefore, in order to match the mocked method, the code should be:
every{webClient.get().uri(any<java.util.function.Function<UriBuilder, URI>>()).retrieve().bodyToMono(???)} returns Mono.just(...)
Or, the any() method can be changed to the real URI which should be as the same as the target method.
Similarly, bodyToMono() should also be mocked with the correct parameter, which may be bodyToMono(any<ParameterizedTypeReference<*>>()).
Finally, the mock code may look like:
every{client.get()
.uri(any<java.util.function.Function<UriBuilder, URI>>())
.retrieve().bodyToMono(any<ParameterizedTypeReference<*>>())}
return Mono.just(...)

Looking for an alternative of retryWhen which is now Deprecated

I'm facing an issue with WebClient and reactor-extra. Indeed, I have the following method :
public Employee getEmployee(String employeeId) {
return webClient.get()
.uri(FIND_EMPLOYEE_BY_ID_URL, employeeId)
.retrieve()
.onStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> Mono.empty())
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new MyCustomException("Something went wrong calling getEmployeeById")))
.bodyToMono(Employee.class)
.retryWhen(Retry.onlyIf(ConnectTimeoutException.class)
.fixedBackoff(Duration.ofSeconds(10))
.retryMax(3))
.block();
}
I've found that I could use retryWhen(Retry.onlyIf(...)) because I want to retry only if a ConnectTimeoutException is thrown. I've found this solution from this post : spring webclient: retry with backoff on specific error
But, in the latest version of reactor the following method became deprecated :
public final Mono<T> retryWhen(Function<Flux<Throwable>, ? extends Publisher<?>> whenFactory)
After hours of googling I haven't found any solution to this question : Is there any alternative for retryWhen and Retry.onlyIf with the latest versions of reactor
Thanks for your help !
Retry used to essentially be a utility function generator distributed as part of reactor-extra. The API has been altered a bit now and brought into reactor-core (reactor.util.retry.Retry), with the old retryWhen() variant deprecated. So no need to include extra anymore - in your case, you can do something like:
.retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(10))
.filter(e -> e instanceof ConnectTimeoutException))
Adding only withThrowable to your existing code can make it work. This has worked for me. You can try something like this :
For example :
.retryWhen(withThrowable(Retry.any()
.doOnRetry(e -> log
.debug("Retrying to data for {} due to exception: {}", employeeId, e.exception().getMessage()))
.retryMax(config.getServices().getRetryAttempts())
.backoff(Backoff.fixed(Duration.ofSeconds(config.getServices().getRetryBackoffSeconds())))))

Resources