How can I mock HTTPResponse interface in Java with ObjectMapper - java

I am trying to mock this HTTPResponse interface, where I am getting an error
public Product handleSolrRequest(CloseableHttpClient client,String query,String limit) throws URISyntaxException, IOException {
HttpGet request = new HttpGet(SolrSchemaUtil.generateURI(solrURL, query, limit));
return client.execute(request, httpResponse ->
mapper.readValue(httpResponse.getEntity().getContent(), Product.class));
}
when I tried the below code,
#Mock
HttpResponse httpResponse;
#Mock
ObjectMapper mockMapper;
#Mock
InputStream stream;
#BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testHandleSolrRequest() throws IOException, URISyntaxException {
when(httpResponse.getEntity().getContent()).thenReturn(stream);
when(mockMapper.readValue(eq(stream), Product.class))
.thenReturn(TestUtil.createSolrProductRS());
...
}
I am getting java.lang.NullPointerException, in the line
when(httpResponse.getEntity().getContent()).thenReturn(stream);
Can someone please help me in understanding what exactly am I missing?

Since httpResponse.getEntity() has no mocking behavior configured, you got the default one which is returning null
Try this
#Mock(answer = Answers.RETURNS_DEEP_STUBS)
InputStream stream;
so your mock will returns mocks as well.
To be honest this one is not a candidate to unit testing but rather integration testing since there is almost NO CODE OF YOURS to test. I would mock web server and check if it behaves correctly with the real request and response (eg using WireMock).
Right now, if you change internal implementation by eg using different http client implementation, you will cause the tests to break, while the public contaract is the same so it should not fail. As a result, such test will discourage any potentially improving changes, instead encouraging it by guaranteeing (is that a word?) that the contract stays intact despite change internal imlementation (this is the core reason of writing tests imo)

Related

Why this java WebClient mock is not working

My server sends a request via WebClient and the code is below:
public String getResponse(String requestBody){
...
WebClient.RequestHeadersSpec<?> request =
client.post().body(BodyInserters.fromValue(requestBody));
String resp =
request.retrieve().bodyToMono(String.class)
.doOnError(
WebClientResponseException.class,
err -> {
// do something
})
.block();
return resp;
}
I wrote a unit test for it and want to mock the WebClient so that I can receive the expected response:
when(webClientMock.post()).thenReturn(requestBodyUriMock);
when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
when(requestHeadersMock.retrieve()).thenReturn(responseMock);
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.just("response"));
String response = someServiceSpy.getResponse(requestBody);
assertEquals(Mono.just("response"), response);
However, the result is not the "response" but a html file. I think I made a mistake somewhere but I don't know how to fix it.
It seems the client referenced in your getResponse method is not set to the mock you have created (webClientMock) in your test.
If you are creating this client object in your getResponse method, I would suggest that you create it using a method that you could mock. Something like
WebClient buildWebClient() {
// build your webclient using the WebClientBuilder
}
You may want to throw a comment and or a #VisibleForTesting annotation on there so it is clear this method exists in order to make testing easier.
Then you can stub this method in your someServiceSpy:
Mockito.doReturn(mockWebClient).when(someServiceSpy).buildWebClient();
This will ensure that your mockWebClient is used in your getResponse method in your test.
Additionally, it seems as though your existing code needs a slight edit.
when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
Should be
when(requestBodyUriMock.body(eq(BodyInserters.fromValue(requestBody)))).thenReturn(requestHeadersMock);
I have figured out the solution that mocks the WebClient directly instead of putting the build logic into a new method to mock it. I wrote my solution here in case someone else needs it future:
Let me put the code example here:
final WebClient client =
WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(someValue))
.clientConnector(new ReactorClientHttpConnector(HttpClient.create(someProvider)))
.baseUrl(someUrl)
.defaultHeader(contentType, TEXT_XML_VALUE)
.build();
final WebClient.RequestHeadersSpec<?> request =
client.post().body(BodyInserters.fromValue(reqBody));
First, we must mock the static method builder() of WebClient. If this method is not mocked, mockito can't edit the behavior of this method, and the mocked WebClient would not be used. I found this from the answer to this StackOverflow question; you can read it for more details.: How to mock Spring WebClient and builder
After mocked the builder() with the method provided by the above anwser, you will get a mocked WebClient, it's something like:
when(webClientBuilder.build()).thenReturn(webClientMock);
Then you can start to finish the rest of the work. In my sample code, the client will invoke post() and body(), so write the following:
when(requestBodyUriMock.body(any())).thenReturn(requestHeadersMock);
when(requestHeadersMock.retrieve()).thenReturn(responseMock);
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.just(expectedResponse));
My unit test returned the NPE at the beginning and it because I used
when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
instead of
when(requestBodyUriMock.body(any())).thenReturn(requestHeadersMock);
I think it is because the code not "think" the requestBodyUriMock is using the BodyInserters.fromValue(requestBody for some reasons that I haven't know yet. After I changed it to any(), it worked.

Trying to use MockMvc together with Wiremock

I'm trying to make a mockMvc call run together with wiremock. But the mockMvc call below in the code keeps throwing 404 instead of the expected 200 HTTP status code.
I know that wiremock is running .. I can do http://localhost:8070/lala through browser when wiremock is running.
Can someone please advise ?
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApp.class)
#AutoConfigureMockMvc
public class MyControllerTest {
#Inject
public MockMvc mockMvc;
#ClassRule
public static final WireMockClassRule wireMockRule = new WireMockClassRule(8070);
#Rule
public WireMockClassRule instanceRule = wireMockRule;
public ResponseDefinitionBuilder responseBuilder(HttpStatus httpStatus) {
return aResponse()
.withStatus(httpStatus.value());
}
#Test
public void testOne() throws Exception {
stubFor(WireMock
.request(HttpMethod.GET.name(), urlPathMatching("/lala"))
.willReturn(responseBuilder(HttpStatus.OK)));
Thread.sleep(1000000);
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.GET, "/lala")) .andExpect(status().isOk());
}
}
By default #SpringBootTest runs with a mocked environment and hence doesn't have a port assigned, docs
Another useful approach is to not start the server at all but to test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost of the full stack is used, and your code will be called in exactly the same way as if it were processing a real HTTP request but without the cost of starting the server
so MockMvc does not pointing to wiremockport (8070) which exactly says 404. If you want do test with wiremock you can use HttpClients like here
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8070/lala");
HttpResponse httpResponse = httpClient.execute(request);
Or you can just use spring boot web integration test feature by mocking any service calling from controller like shown here

Is MockMvc eligible for WebFlux controllers testing?

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.

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.

Jersey: making the client to throw the same server exception

I understand that a Jersey-based web service is able to associate exceptions thrown by service methods to desired HTTP return codes (here). Now, is there any chance to make the client generate exactly the same exception that was generated by the service method? I mean, if the server side throws MySpecificException, is there a way to store such information (i.e., the FQN of the exception class) in the HTTP response (automatically, I don't want to turn to methods that build the response explicitly, I want them to return POJOs or void), so that the client can use it to re-throw the same exception?
REST does not specify exception as a response and thus there's no straightforward way to do this (this is not RPC).
However, you can introduce your own convention. For example:
On the provider side you could define ForbiddenException:
public class ForbiddenException extends WebApplicationException {
public ForbiddenException(String code, String readableMessage) {
super(Response.status(Status.FORBIDDEN).entity(new ForbiddenEntity(code, readableMessage)).build());
}
}
(You should probably compose response in ExceptionMapper instead of exception itself, but this is just for demonstration purposes).
And on the consumer side - ClientFilter:
public class ForbiddenExceptionClientFilter extends ClientFilter {
#Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
ClientResponse response = getNext().handle(cr);
if (response.getStatus() == Status.FORBIDDEN.getStatusCode()) {
ForbiddenEntity reason = response.getEntity(ForbiddenEntity.class);
throw new RemoteResourceForbiddenException(reason.getCode(), reason.getReadableMessage());
}
return response;
}
}
This works as long as server complies with the convention and client uses the client filter.
Please note, this is not "exactly the same" exception - stacktrace is not transferred, however this is the right thing to do as it does not make any sense in client application. If you need stacktrace - it should be printed to logs using ExceptionMapper on server side.

Categories