Is MockMvc eligible for WebFlux controllers testing? - java

I have a simple WebFlux application (that uses controllers, not router functions). The only non-standard part is that it uses Server-Sent-Events.
An interesting part of the controller is
#GetMapping(path = "/persons", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Object>> persons() {
return service.persons()
.map(this::personToSse)
.onErrorResume(e -> Mono.just(throwableToSse(e)));
}
private ServerSentEvent<Object> personToSse(Person person) {
return ServerSentEvent.builder().data(person).build();
}
Service:
public interface Service {
Flux<Person> persons();
}
I have two tests:
#SpringBootTest(classes = Config.class)
#AutoConfigureMockMvc
class PersonsControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
void streamsPersons() throws Exception {
when(service.persons())
.thenReturn(Flux.just(new Person("John", "Smith"), new Person("Jane", "Doe")));
String responseText = mockMvc.perform(get("/persons").accept(MediaType.TEXT_EVENT_STREAM))
.andExpect(status().is2xxSuccessful())
.andExpect(content().string(not(isEmptyString())))
.andReturn()
.getResponse()
.getContentAsString();
assertThatJohnAndJaneAreReturned(responseText);
}
#Test
void handlesExceptionDuringStreaming() throws Exception {
when(service.persons())
.thenReturn(Flux.error(new RuntimeException("Oops!")));
String responseText = mockMvc.perform(get("/persons").accept(MediaType.TEXT_EVENT_STREAM))
.andExpect(status().is2xxSuccessful())
.andReturn()
.getResponse()
.getContentAsString();
assertThat(responseText, is("event:internal-error\ndata:Oops!\n\n"));
}
First test checks that for the 'sunny day scenario' we get two persons that we expect. Second test checks what happens when an exception occurs.
The tests work perfectly when I run them one by one. But when I run them both, sometimes they pass, sometimes one of them fail, sometimes both fail. Failure reasons are different:
Sometimes Jackson complains during JSON parsing that an EOF was reached ('No content to map due to end-of-input', although in the log I can see a valid full JSON)
Sometimes first test fails and second passes as if in both cases an error was returned, even though I can see in the logs that for the first test normal response was generated, not the erroneous one
Sometimes second test fails and first passes as if in both cases valid JSONs where returned
So it looks like there is some concurrency problem. But my test code is simple enough, it does not use any concurrency-related concepts.
The following test fails 100% of times on my machine (it just runs these 2 tests repeatedly 1000 times):
#Test
void manyTimes() throws Exception {
for (int i = 0; i < 1000; i++) {
streamsPersons();
handlesExceptionDuringStreaming();
}
}
The questions follow:
Can MockMvc be used to test reactive controllers at all?
If it can, do I do anything incorrectly?
Here is the full project source code: https://github.com/rpuch/sse-webflux-tests
The manyTests() method is commented out and has to be re-enabled to be used.

1. Can MockMvc be used to test reactive controllers at all?
The answer is no, MockMvc is a blocking mockClient that will call your method once and return. It does not have the ability to read items consecutively as they get emitted The client you need to use is the Spring WebClient.
You can read more here how to go about testing infinite streams using the Spring WebTestClient.
2. If it can, do I do anything incorrectly?
See the answer to question one.

Related

Most efficient way to track execution time of methods in REST API? (Java Spring Boot app, Swagger)

I have a Java Spring Boot API (Swagger) that I is having thousands of calls on it daily. I wanted to log the execution times of these methods for analysis. I used Spring AOP (Aspect Orientated Programming) to make a simple interface and concrete class that allows me to annotate my methods with #TrackExecutionTime to see the runtime. I have listed the code for that below. My problem is, that in the logs, I am tracking the method call times, but I have thousands of requests, so I need a way of "tagging" each api call and logging it, so I can follow the flow of each api call. I was thinking of generating a random # or maybe someone here had a better suggestion. So the logging currently looks like this:
"com.mypackage.myclassname.mymethodname. Time taken for Execution is : 100ms"
Also, My first RestController uses Swagger, so I tried to annotate the method with my #TrackTimeExecution, but we are using the swagger-codegen-maven plugin, so it reads the swagger definition yaml file and generates the "CustomerApi" and other classes/interfaces when it compiles. When I tried annotating at the class level per below, the Spring Boot app compiles, but when I run the app locally on port 8080 and try to hit the endpoint I annotated with Postman, nothing happens at all. It's like annotating breaks the swagger codegen or something, so I had to settle with sticking the annotation on the customersService.getCustomers() method. Is this acceptable? I figured I would need to clock the execution from when the Controller first gets hit, but as I said, I coudln't do it this way unless I'm making some dumb mistake, so I had to put it on the next method the controller calls. Does this make my timing of the api call inaccurate since I would need to time in when the app first gets the request by the controller? Would love any input here...
Dumbed dumb implementation of one of my endpoints, basically the same:
#RestController
#TrackExecutionTime // this fails to compile
public class CustomerApiController implements CustomerApi {
#Autowired
public CustomerApiController(ObjectMapper objectMapper, HttpServletRequest request) {
this.objectMapper = objectMapper;
this.request = request;
}
public ResponseEntity<List<Customer>> searchCustomer() {
return new ResponseEntity<List<Customer>>(this.customerService.getCustomers(), HttpStatus.OK);
Class that logs the execution time of any method annotated with "#TrackExecutionTime"
#Aspect
#Component
#Slf4j
#ConditionalOnExpression("${aspect.enabled:true}")
public class ExecutionTimeAdvice {
#Around("#annotation(com.mailshine.springboot.aop.aspectj.advise.TrackExecutionTime)")
public Object executionTime(ProceedingJoinPoint point) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = point.proceed();
long endtime = System.currentTimeMillis();
log.info("Class Name: "+ point.getSignature().getDeclaringTypeName() +". Method Name: "+ point.getSignature().getName() + ". Time taken for Execution is : " + (endtime-startTime) +"ms");
return object;
}
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TrackExecutionTime {
}
To use this annotation over class you have to change target of the annotation to ElementType.TYPE instead of ElementType.METHOD.

Only first test in class failed when running multiple classes

I have a really strange problem when running multiple tests in intellij. My first test in class failed because apparently jwt was not valid, but then in the second test in the same class everything is working fine with the same jwt.
If I try to run every class separately everything works fine, also if I run mvn test from the terminal, all tests will pass.
This is my configuration for the test in intellij
I also tried to set Fork mode to class, then test passed, but every test run separately, so it takes a while, and also I can't use coverage in Fork mode.
edit:
This is my code for init and first test
#BeforeAll
void init() throws Exception {
token = login(new LoginRequest("user", "123"));
}
private String login(LoginRequest loginRequest) throws Exception {
ResultActions resultActions = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andExpect(status().isOk());
return resultActions.andReturn().getResponse().getHeader(JwtUtilities.HEADER);
}
#Test
#Order(1)
void findAllTeamsByCreator_ok() throws Exception {
mockMvc.perform(get(PATH)
.contentType(MediaType.APPLICATION_JSON)
.header(JwtUtilities.HEADER, token))
.andExpect(status().isOk())
.andExpect(content().contentType("application/hal+json"))
.andExpect(jsonPath("$._embedded.teamDtoList").isNotEmpty());
}
so, I'm not setting any state in my test, and if I create test like this
#Test
#Order(1)
void test() {}
and then everything else remains the same, my tests will work. It's like something is not loaded in first test.
And it's really strange to me why mvn test works fine, but in Intellij I have bug
This sounds like something is "state full" in your test cases, so something in the first test is initializing something that is needed in the remaining test cases.
As you are using JUnits, you might make sure all the initialization is done in one of the "Before"-type methods that the framework supports.
It would be helpful to see the code of your first test case to determine what has "state".

How to write test so that the controller can connect to API?

I'm trying to deal with Rest api tests.
Well, the controller in my code connects to the external api parsing and returns in the form of JSON.
I'm trying to run a test so that it returns the result of my application’s logic.
Unfortunately, during testing I can't connect with api github.
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
#WebMvcTest(RepositoryDetailsController.class)
#ContextConfiguration(classes = RepositoryDetailsController.class)
class RepositoryDetailsControllerTestt {
#Autowired
private MockMvc mvc;
#MockBean
private RepositoryDetailsService service;
#InjectMocks
private RepositoryDetailsController repositoryDetailsController;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(repositoryDetailsController).build();
}
#Test
public void mockTest() throws Exception {
RepositoryDetailsResponse details = new RepositoryDetailsResponse();
details.setDescription("Ruby toolkit for the GitHub API");
details.set"https://github.com/octokit/octokit.rb.git");
details.setStars("57892");
details.setName("octokit/octokit.rb");
mvc.perform(get("repository/{owner}/{repository}","octokit","octokit.rb")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*", hasSize(5)))
.andExpect(jsonPath("$.fullName").value(details.getName()))
.andExpect(jsonPath("$.description").value(details.getDescription()))
.andExpect(jsonPath("$.cloneUrl").value(details.getUrl()))
.andExpect(jsonPath("$.stars").value(details.getStars()));
}
If you can avoid it you usually don't want to connect to an external API in your tests. Having an external API test dependency makes life harder e.g. what if the API is down or what if the API had a breaking change?
There are few tools that you can use to mock this external API, hardcoding the expected responses while still making the request:
Wiremock - very generic, exposes a mock server that can be used with any library that makes the API call
Spring's MockRestServiceServer - especially useful if your code is using RestTemplate to make the API call
Test Containers - if you have a Docker image that provides a mock API
In your example it looks like you attempted to use MockMvc instead of MockRestServiceServer but there is not enough code to tell what you are doing exactly.

Spring. JUnit5. WebClient. How to test async calls

I have a Spring boot project with a service, which basically calls a private method, which does:
webClient.post().uri().accept(...).body(...).exchange()
and then puts a subscribe(...) on it, which simply logs the result.
Everything works fine, but now I need to test this, and this is where things start to get interesting.
By far, I've tried MockServer, okhttp, Spring's WebMockServer(or something), and only MockServer was willing to work at some point properly, while okhttp latest wants junit.rules.* (which is problematic to
achieve), WebMockServer specifically wants RestTemplate.
Google does give out examples, in which a webClient logic method is left without a .exchange() call, giving a chance to call .block() in the test, but I'm not willing to expose a private method just in order to workaround the async calls.
Currently I'm struggling with DEEP_STUB strategy of Mockito to mock out the actual webClient chain, but this fails to work from the box, and I'm trying to make it work while writing this question.
So the question is - is there a proper way to test a webClient with an async call (maybe a MockServer with a timeout to the verification or something)?
Wiremock seem to be an accepted way of mocking external http servers and it is now part of spring test framework.
Here is an introduction: https://www.baeldung.com/introduction-to-wiremock
Otherwise WebTestClient is a bean which might come in handy at this point if you can inject it in the constructor of your service class?
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWebClient
class DemoResourceTest {
#Autowired
private WebTestClient webTestClient;
#BeforeEach
public void setUp() {
webTestClient = webTestClient
.mutate()
.responseTimeout(Duration.ofMillis(1000))
.build();
}
#Test // just for reference
void some_test_that_gives_success() {
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/world")
.queryParam("requestString", "hello")
.build())
.accept(APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(String.class);
}
}

What are the pros & cons of 2 types of spring testing

In my current project we are working on microservices(web app).
In unit tests we try to cover 85-90% of our code. I have noticed 2 approaches of testing using spring:
Inject a controller and invoke its methods directly
Form a proper request where you can specify cookies, headers... and then make a call
Moreover, we won't be able to test authentication with the 1 approach.
Which of the next spring testing ways should be used? And what are the (dis-)advantages of each type?
#RestController
class MyController {
#PostMapping(path="/path")
public String handle(#RequestBody MyRequest request) {
//service invoked
return "some value";
}
}
JUnit approach #1
#LotsOfAnnotations
class ControllerTest1 {
#Autowired
private MyController myController;
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
MyRequest request = new MyRequest();
// Invoke testing method
myController.handle(request);
// Assert
}
}
JUnit approach #2
#LotsOfAnnotations
class ControllerTest2 {
#Autowired
private TestRestTemplate testTemplate;
private MockRestServiceServer server;
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webAppContext;
#Before
public void setup() {
server = MockRestServiceServer.createServer(testTemplate.getRestTemplate());
mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext).build();
}
#Test
public String verboseNameTest() {
// Mock 3rd party calls
....
// Form request
String jsonStringRequest = "{}";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/path")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStringRequest);
// Make a call
MvcResult result = this.mockMvc
.perform(requestBuilder)
.andExpect(status().isOk())
.andReturn();
// Assert
}
}
As far as I can see you need to understand when to mock the service and when to make a direct call.
In your first approach you are directly calling the web service so let's say for example you have a huge code base and multiple developers are working at a time so you would get constant updates in the classes and if you want to test your piece of code(which is why you use JUnit) then you making direct call can result in error/ failed test case because someone might have changed something.
In second approach or mocking basically removes that possibility by mocking the other service which your code would be needing so that will make you code test easier.
But there are people who questions what's the use of mocking when you are not even testing the whole thing which is true up to certain point but the main reason for mocking is to test your piece of code only irrespective of other services that code is dependent on. Also if you talk about making actual service call that should be part of integration testing or end to end testing.

Categories