Jackson Error When Doing Integration Test with Sprint Boot & GraphQL - spring-boot

I'm trying to write integration tests for my GraphQL code with Spring Boot and having problems.
First, this is the library I'm using for GraphQL Spring Boot integration:
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>7.0.1</version>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter-test</artifactId>
<version>7.0.1</version>
<scope>test</scope>
</dependency>
I've got all my resolvers built perfectly, I can call them from postman no problem. It's my test code where the problem comes up. I want to do integration tests, running end-to-end. For my first test, here is my GraphQL query file:
query {
clients {
id
name
clientKey
enabled
allowAuthCode
allowClientCredentials
allowPassword
}
}
This is a perfectly valid query for my GraphQL API, I can run it from Postman successfully. Here is my test code:
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ClientQueryIntegrationTest {
#Autowired
private lateinit var graphqlRestTemplate: GraphQLTestTemplate
#Test
fun test() {
graphqlRestTemplate.addHeader("Authorization", "Bearer $token")
val response = graphqlRestTemplate.postForResource("graphql/getAllClients.graphql")
}
}
When I run this, I get a Jackson deserialization error with my initial GraphQL request. I know it is loading my GraphQL query correctly because of the information in the error message, I just don't know why Jackson is de-serializing this incorrectly during my tests.
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `graphql.kickstart.execution.GraphQLRequest` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"query":"query {\n clients {\n id\n name\n clientKey\n enabled\n allowAuthCode\n allowClientCredentials\n allowPassword\n }\n}\n","variables":null}')
at [Source: (String)""{\"query\":\"query {\\n clients {\\n id\\n name\\n clientKey\\n enabled\\n allowAuthCode\\n allowClientCredentials\\n allowPassword\\n }\\n}\\n\",\"variables\":null}""; line: 1, column: 1]
What can I try next?

Related

Argument passed to when() is not a mock! exception thrown with Spring Boot project which doesn't have #SpringBootApplication/main class

My project is a simple spring boot application which doesn't have a main/#SpringBootApplication class. It is used as a dependency library for other modules. I am trying to write the unit tests for the classes present in this project like below and getting the below pasted error. Any quick help is much appreciated.
pom dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
As this project doesn't have main class, to get the spring application context using below configuration class.
#Configuration
public class TestServiceConfig {
#Bean
public TestService productService() {
return Mockito.mock(TestService.class);
}
#Bean
public MongoDriverService productMongo() {
return Mockito.mock(MongoDriverService.class);
}
}
Below is my test class which is throwing exception. Actual java class has a method called getPlanCode(which takes 6 arguments) and returns void. In this method mongo object is used for connecting the db so that I used #InjectMocks on service object.
public class ValidationServiceTest {
#Mock
MongoDriverService mongo;
#InjectMocks
TestService service;
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
doNothing().when(service).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
service.getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
verify(service, times(1)).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
}
}
Below is the exception
12:51:33.829 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#45b4c3a9 testClass = DefaultMedicareBFTAccumsValidationServiceTest, testInstance = com.anthem.rxsmart.service.standalone.batchvalidation.DefaultMedicareBFTAccumsValidationServiceTest#14dda234, testMethod = getPlanCodeTest#DValidationServiceTest, testException = org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
service is not a mock since you are using #InjectMocks ( assume you are using #RunWith(MockitoRunner.class) or #ExtendWith but you are hiding that for whatever reasons).
What #InjectMocks does, is create of a new instance of TestService and literally inject mocks into it (mocked required dependencies). So service is a real thing, not a mock
IMO this test makes not sense as you are suppose to test your implementation of singular entity contract, not to test mocks...
Your test case and assertions are pointless as it is like "call method A and check if I just called method A" while you should check and validate eg return value of a call, or if some methods of mocks have been called eg if Mongo was queried with proper arguments. I just hope it is a really bad example, not real test scenario
Also test setup is wrong as you show us that you want to use #Configuration class with #Bean but then you are using #Mock in the test which will create brand new mocks for you. In other words - that config is not used at all
Posting this answer just for the developers who are in same understanding state.
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
service = new ValidationService(mongo);
Mockito.when(mongo.aggregateIterable("test", pipeline)).thenReturn(tierFilterDocs);
service.getPlanCode("", "", null, batchFile, null, "");
verify(mongo, times(1)).aggregateIterable("test", pipeline);
}
I have updated my test case so it solves the purpose now. Now no need of the Configuration file as I am mocking the object in test class itself.

Spring Boot WebClient XML

My spring boot application wants to use Webclient to make an http request (XML request body) and receives XML response. Hence I created another spring boot application with jackson-dataformat-xml and created an endpoint to receive and return XML as below.
spring-boot-version=2.2.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
#PostMapping(value = "/api",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<MyXmlResponse> trip(#RequestBody MyXmlRequest request) throws Exception {
MyXmlResponse response = new MyXmlResponse();
response.setStatus("SUCCESS");
response.setTripID(request.getTripID());
return ResponseEntity.ok().body(response);
}
It works perfect and obviously no JaxB annotations are required as I use jackson-dataformat-xml. Also the request XML can be case-insensitive.
Now, in my first application I want to consume this API via webclient. I read that Spring webflux do not support Jackson-dataformat-xml yet. Hence I have to annotate my classes with Jaxb annotations.
spring-boot-version=2.2.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
webClient.post()
.uri(URI.create("url-to-api-endpoint"))
.body(Mono.just(myXmlRequest), MyXmlRequest.class)
.exchange()
.doOnSuccess(response -> {
HttpStatus statusCode = response.statusCode();
log.info("Status code of external system request {}", statusCode);
})
.doOnError(onError -> {
log.error("Error on connecting to external system {}", onError.getMessage());
})
.flatMap(response -> response.bodyToMono(MyXmlResponse.class))
.subscribe(this::handleResponse);
Above code throws an exception as follows
org.springframework.webreactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlRequest
at org.springframework.web.reactive.function.BodyInserters.unsupportedError(BodyInserters.java:391)
I fixed this problem by annotating with XmlRootElement as follows
#Getter #Setter #NoArgsConstructor #ToString
#XmlRootElement()
public class MyXmlRequest {
private String attribute1;
}
On the next attempt I got another error as follows
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlResponse
Caused by: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/xml' not supported for bodyType=com.example.MyXmlResponse
This could be solved by annotating MyXmlResponse with XmlRootElement as follows
#Getter #Setter #NoArgsConstructor #ToString
#XmlRootElement()
public class MyXmlResponse {
private String attr1;
private String attr2;
}
This time I get unmarshallexception as follows
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.core.codec.DecodingException: Could not unmarshal XML to class com.example.MyXmlResponse; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[com.sun.istack.internal.SAXParseException2; lineNumber: 1; columnNumber: 15; unexpected element (uri:"", local:"MyXmlResponse"). Expected elements are <{}myXmlResponse>]
Caused by: org.springframework.core.codec.DecodingException: Could not unmarshal XML to class com.example.MyXmlResponse; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
I fixed it with additional attributes passed to annotation as follows.
#XmlRootElement(name = "MyXmlResponse", namespace = "")
public class MyXmlResponse {
In future, my XML structures going to be tremendously complex. I want to know if I am doing it the right way.

Springboot throwing giving Jackson exceptions

During testing , I have faced the issue.I have published a rest API with a controller class with a model input .
While Calling the API , instead of a single string , an array [{"a":1,"b":2}] has been used. Which triggered the following error:
{
"timestamp": "2018-12-19T12:33:36.729+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 3, column: 14] (through reference chain: com.xy.df.model.inputReq[\"req\"])",
"path": "x/y/z"
}
We did not imported JACKSON dependency in application , explicitly in POM. I have noticed in the parent pom jackson version used is :2.9.5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
1.Is it vulnerable for RCE? How to resolve this in Spring-boot ?
2. How I can supress/override the exception message so that client never gets to know what libraries used underneath ?
JsonMappingException: out of START_ARRAY token exception is thrown by Jackson object mapper as it's expecting an Object {} whereas it found an Array [{}] in response.
This can be solved by replacing Object with Object[] in the argument for geForObject("url",Object[].class).
References:
Ref.1
Ref.2
Ref.3
I have resolved issue . Before going ahead , one needs to understand couple of very useful annotations-
#ExceptionHandler - This handler helps you define an error class for which you want to catch the exception
#controller advice - It caters a cross cutting approach . Any class mentioned as controller advice , it is available for all the controller under your microservice.
#ControllerAdvice
public class ExceptionController {
#Autowired
SomeGenericResponse someGenericResponse ; /* data model of common response */
#ExceptionHandler(value = <My case Jackson Class>.class)
public ResponseEntity<SomeGenericResponse> CustomException(HttpServletRequest req, HttpServletResponse res,Exception ex) {
someGenericResponse.setMessage("Your Message");
someGenericResponse.setStatus("false");
return new ResponseEntity<SomeGenericResponse> someGenericResponse ,HttpStatus.BAD_REQUEST);
}
}

Spring Boot Actuator Trace HTTP POST request parameters is coming empty

I am using Spring Boot 1.5.13 so using Actuator 1.5.13 . When i call with the post request ,request parameters is coming empty.
No other configuration or createing my Actuator repository.
I just using simple actuator trace end point.
Here is my properties:
endpoints.trace.enabled=true
endpoints.trace.sensitive=false
management.trace.include=request-headers,response-headers,cookies,errors,parameters
Here is the dependency :
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Trace result :
parameters {}
Request Example :
{
"id": 22,
"secondid":350052 ,
"flag":0
}
Do you have any idea why it is happening and how i can fix it?
The parameters that are traced are those that are available from javax.servlet.ServletRequest.getParameterMap(). From its javadoc:
Request parameters are extra information sent with the request. For HTTP servlets, parameters are contained in the query string or posted form data.
Your HTTP request doesn't have a query string and it isn't POSTing form data so there are no parameters to trace.
For example if request is like /api/persons?name=peter, parameters will be available in the trace.
"parameters": {
"name": [
"peter"
]
}

Testing with spring-test-mvc jsonpath returns null

I am using Spring's "spring-test-mvc" library to test web controllers. I have a very simple controller that returns a JSON array. Then in my test I have:
#Test
public void shouldGetAllUsersAsJson() throws Exception {
mockMvc.perform(get("/v1/users").accept(MediaType.APPLICATION_JSON))
.andExpect(content().mimeType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("fName").exists());
}
The above test returns:
java.lang.AssertionError: No value for JSON path: fName
To quickly check what I actually get I ran the below test:
#Test
public void shouldPrintResults() throws Exception {
mockMvc.perform(get("/v1/users").accept(MediaType.APPLICATION_JSON))
.andDo(print());
}
And it returns the correct JSON array in the body of MockHttpServletResponse
I'm not sure why jsonPath is not able to see fName in the JSON array.
If you add the json path dependency to maven, or add the jar to your lib, then it will work. I think that Spring is not including the jsonPath dependency in the latest Spring 3.2.0 RC1 release. I'm guessing that this is the same for Spring-Test-MVC standalone project as well.
Here is the dependency for Maven:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
You might also need the hamcrest library to use the jsonPath("$.test").value("test")
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
What does your json response body look like? You can see it by doing an .andDo(print())
You might want to try jsonPath("$.fName").
This is assuming that your json response is:
{"fName":"first name"}
If your response is an array then you need jsonPath("$[0].fName") for a response like:
[{"fName":"first name"},{"fName":"first name #2"}]
You can see more examples at: http://goessner.net/articles/JsonPath/

Resources