How can I test a handler method that receive ServerRequest and return Mono<ServerResponse> ?
I can create a ServerRequest via org.springframework.mock.web.reactive.function.server.MockServerRequest.builder(). And assert on the ServerResponse.statusCode(). However I would like to test the body of this ServerResponse but there is no way to extract it.
ServerResponse response = target.search(MockServerRequest.builder()
.method(HttpMethod.GET)
.build()
).block();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
//assertThat(response.body()).isNotNull();
I don't want to do a broader test with the WebTestClient, I would like to test all possible cases of response with unit tests.
Thanks
So, it seems that the best solution is to use the WebTestClient. However this one can be used without a running server;
The spring-test module includes a WebTestClient that can be used to test WebFlux server endpoints with or without a running server.
-- https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html#web-reactive-tests
The trick is to use the bindTo..() builder method. In my case bindToRouterFunction(new MyRouter(new MyHandler())) works well
I had exactly the same question, because I don't want to start the entire Spring context for every test. Thanks for the info in the own-answer, this helped me. Here is a complete example including Mockito and JUnit5:
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
#ExtendWith(MockitoExtension.class)
class UsersHandlerTest {
#Mock
UserRepository userRepository;
WebTestClient client;
#BeforeEach
public void setUp() {
UsersHandler usersHandler = new UsersHandler(userRepository);
Router router = new Router(usersHandler);
Mockito.when(userRepository.findAll()).thenReturn(
Arrays.asList("", ""));
client = WebTestClient.bindToRouterFunction(router.getUsers()).build();
}
#Test
public void getCommands() {
client.get().uri("/users")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectBody().json("{}");
}
}
I have asked a similar question insisting not to use WebTestClient.
I have ended up answering my own question after discovering that it is possible to cast the response to a type that allows inspecting body (entity).
The magic line (Kotlin) is:
(serverResponse as EntityResponse<Fleet>).entity()
Edit:
So for the case of expecting an empty body, you could do:
assert(serverResponse !is EntityResponse<*>)
Thanks for #andred for pointing this out
Related
Introduction
I have a microservice responsible for authenticating each API call to my server. so when any request comes API gateway is supposed to call the authenticator for the validation with token and after this is a success the actual API call process to the intended destination.
API Gateway is a spring-cloud-starter-gateway project using spring-boot-starter-webflux. And the other microservice can be connected using FeignClient.
Problem
I am trying to call the feignclient method from the ReactiveAuthenticationManager authenticate(). and I need some kind of possibility to intercept it and validate this to either throw error or return a success mono.
But as of now the flatmap is not being called.
Code
import com.atlpay.pgapigateway.JwtAuthenticationToken
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.server.context.ServerSecurityContextRepository
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
#Component
class SecurityContextRepository : ServerSecurityContextRepository {
#Autowired
private lateinit var authenticationManager: AuthenticationManager
override fun save(swe: ServerWebExchange, sc: SecurityContext): Mono<Void> {
return Mono.empty()
}
override fun load(serverWebExchange: ServerWebExchange): Mono<SecurityContext> {
val path = serverWebExchange.request.path
//send request to authentication manager
val auth: Authentication = JwtAuthenticationToken(
"",
serverWebExchange.request.method!!.name,
path.toString(),
serverWebExchange.request.headers
)
return this.authenticationManager.authenticate(auth)
.map { authentication ->
SecurityContextImpl(authentication)
}
}
}
The above SecurityContextRepository calls the AuthenticationManager mentioned below.
import com.atlpay.pgapigateway.JwtAuthenticationToken
import com.atlpay.pgjavacontracts.feign.TestFeign
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.core.Authentication
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
#Component
class AuthenticationManager : ReactiveAuthenticationManager {
#Autowired
private lateinit var testFeign: TestFeign
override fun authenticate(authentication: Authentication): Mono<Authentication> {
val authToken = (authentication as JwtAuthenticationToken).token
val toReturnUserNamePasswordAuthenticationToken = JwtAuthenticationToken(
authentication.token,
authentication.httpMethod,
authentication.path,
authentication.httpHeaders
)
return testFeign.getUsers()
.flatMap { response ->
//if error throw error
>>>>>> println("This is just a test >> ${response.status()}")
Mono.just(toReturnUserNamePasswordAuthenticationToken as Authentication)
}
.then(Mono.just(toReturnUserNamePasswordAuthenticationToken as Authentication))
}
}
From the above class I am expecting to intercept the call the validate the response code of the feignclient. at this line -
println("This is just a test >> ${response.status()}")
But this is never called!!
FeignClient is just a mock api for testing -
import feign.Response
import org.springframework.web.bind.annotation.PostMapping
import reactivefeign.spring.config.ReactiveFeignClient
import reactor.core.publisher.Mono
#ReactiveFeignClient(url = "https://630369f20de3cd918b34e39e.mockapi.io", name = "testFeign")
interface TestFeign {
#PostMapping("/users")
fun getUsers(): Mono<Response>
}
Attempts
Tried to make a call using Restclient blocking api. and got the result.
but I want it to be load balanced so changed to a LoadBalanced rest client and used the URL 'http://myMicroServiceName/validate' this gives me a block()/blockfirst()/blocklast() are blocking error.
So this is out of scope for now.
I think I am missing some basic structure in the Reactive part or its an issue with the Apigateway ReactiveAuthenticationManager.
Update
Found an solution with webfilter. will update with an answer soon.
Any suggestion is appreciated.
I'm trying to do something similar to:
mockMvc
.perform(post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
But the .content method has disappeared with 5.2.6, there is now what seems to be a body() function:
mockMvc
.perform(post("/conversions/ktoc")
.contentType(MediaType.APPLICATION_JSON)
.body(objectMapper.writeValueAsString(request)). /*missing method to get back to Builder*/
.andExpect(status().isOk());
Can someone assist please, all the examples I find on web all point to using content(..) method
There should be still the .content() method in Spring Test 5.2.6 according to the docs.
What might have happened to your code is that your import the reactive version of MockMvcRequestBuilders from org.springframework.mock.http.server.reactive.MockServerHttpRequest.post and this has indeed no .content() method but only .body().
So insure that your import the servlet version inside your test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WebMvcTest
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testMe() throws Exception {
this.mockMvc
.perform(post("/test").content("Some content"))
.andExpect(status().isOk());
}
}
I've put a very simple sample project on GitHub to reproduce the problem.
The main issue is that I have a PersonController that has a PutMapping to create a new person. In order to populate the Location header with the URL to fetch that person, I add the UriComponentsBuilder as parameter for that PutMapping, as you can see here:
#PostMapping
public ResponseEntity<Person> add(#RequestBody final PersonForCreate personForCreate, UriComponentsBuilder uriComponentsBuilder) {
Person newPerson = new Person(this.people.size() + 1, personForCreate.getFirstName(), personForCreate.getLastName());
this.people.add(newPerson);
// create the URI for the "Location" header
MvcUriComponentsBuilder.MethodArgumentBuilder methodArgumentBuilder = MvcUriComponentsBuilder.fromMappingName(uriComponentsBuilder, "getById");
methodArgumentBuilder.arg(0, newPerson.getId());
URI uri = URI.create(methodArgumentBuilder.build());
return ResponseEntity.created(uri).body(newPerson);
}
This works fine when running the project. But when running a test this results in an IllegalArgumentException No WebApplicationContext. The error comes from the MvcUriComponentsBuilder.fromMappingName call, but I have no idea why.
My test looks as follows:
#ExtendWith(SpringExtension.class)
#WebMvcTest
class PersonControllerTest {
#Autowired
private PersonController personController;
#Test
void add() {
this.personController.add(new PersonForCreate("Charles", "Darwin"), UriComponentsBuilder.newInstance());
}
}
I'm not sure if passing UriComponentsBuilder.newInstance() is correct, but I've tried with other values and notice no difference.
FYI, The sample project uses Spring Boot 2.2.3 and JUnit 5, but I have the same problem using a sample project on JUnit 4.
Did you try MockMvc? The following code will be called in the same way HTTP request gets processed, as you're using #WebMvcTest, only the web layer is invoked rather than the whole context.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
#WebMvcTest
class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
void add() throws Exception {
//this.personController.add(new PersonForCreate("Charles", "Darwin"), uriComponentsBuilder);
this.mockMvc.perform(MockMvcRequestBuilders.post("/person")
.content("{\"firstName\": \"Charles\",\"lastName\": \"Darwin\"}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(MockMvcResultMatchers.content().string("{\"id\":4,\"firstName\":\"Charles\",\"lastName\":\"Darwin\"}"));
}
}
Spring.io/guides reference
https://spring.io/guides/gs/testing-web/
I'm trying to create a test class for some of my services but I haven't been able to mock my DAOs, which are annotated as seen here:
import org.springframework.stereotype.Repository;
#Repository
public class TestDAO {
}
This is my test class
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
class ArchivoTest {
#Mock
public TestDAO testDAO;
#Test
public void test() {
System.out.print(testDAO == null);
}
}
When I run the test with JUnit archivoDAO is always null, and I don't know what to do about it or if I'm missing something. I'm a complete noob at mockito, but a half an hour search through google solved nothing, so I guess it's something not that obvious. Thanks in advance
We are doing a little hackathon at work and I wanted to try some new technology to get away from the usual controller.
I started using Spring Webflux with reactive WebSockets and everything is working fine so far. I configured my WebSocket handler as follows:
import my.handler.DomWebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class AppConfig implements WebFluxConfigurer {
#Autowired
private WebSocketHandler domWebSocketHandler;
#Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/event-emitter", domWebSocketHandler);
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(1);
mapping.setUrlMap(map);
return mapping;
}
#Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
After some more research, I learned that it is best practice to work with one connection per client.
Furthermore using more than a web-socket per browsing session for the same application seems overkill since you can use pub/sub channels. See answer here
Is there a way to restrict the connections per client and use only one endpoint for all required client "requests" or would it be better to create additional endpoints (like you would with a normal controller)?
Thank you in advance for the help.