JAVA Contract testing (CDC) using Pact - java

I'm trying to write contract test to this service:
#RestController
#RequestMapping(path = "/api/form")
public class FormController {
private RestOperations restOperations;
#Autowired
public FormController(RestOperations restOperations) {
this.restOperations = restOperations;
}
#PostMapping(path = "/submit", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<SubmitFormResponse> submitForm(#RequestBody #Valid SubmitFormCommand submitFormCommand) {
return restOperations.postForEntity("http://127.0.0.1:9000/api/form/submit", submitFormCommand, SubmitFormResponse.class);
}
}
SubmitFormCommand contains only String "message" and SubmitFormResponse contains Boolean "success"
My RestClient for this service:
#Component
public class FormControllerClient {
#Autowired
private RestOperations restOperations;
public ResponseEntity<SubmitFormResponse> submitForm(SubmitFormCommand submitFormCommand) {
HttpEntity<SubmitFormCommand> request = new HttpEntity<>(submitFormCommand);
return restOperations.exchange("http://localhost:1234/api/form/submit", HttpMethod.POST, request, SubmitFormResponse.class);
}
And Contract test class of consumer looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ContactFormClientTest {
#Rule
public PactProviderRuleMk2 pactProviderRuleMk2 = new PactProviderRuleMk2("formservice", "localhost", 1234, this);
#Autowired
private FormControllerClient formControllerClient;
#Pact(state = "provider accets submit contact form", provider = "formservice", consumer = "formclient")
public RequestResponsePact submitFormPact(PactDslWithProvider builder) {
return builder
.given("provider accetps form submit")
.uponReceiving("a request to POST form")
.path("/api/form/submit")
.method("POST")
.willRespondWith()
.status(200)
.matchHeader("Content-Type", "application/json;charset=UTF-8")
.body(new PactDslJsonBody()
.stringType("message", "TestMessage"))
.toPact();
}
#Test
#PactVerification(fragment = "submitFormPact")
public void verifySubmitFormPact() {
SubmitFormCommand submitFormCommand = new SubmitFormCommand("TestMessage");
ResponseEntity<SubmitFormResponse> response = formControllerClient.submitForm(submitFormCommand);
assertNotNull(response);
}
}
Every time I run the test it says "Connection refused" and I don't understand what I did wrong with a setup, my FormController would be a consumer in this case since it calls another service to submit the form.
Plugin in pom.xml for building Pact file looks like this :
<plugin>
<!-- mvn pact:publish -->
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.11</artifactId>
<version>3.5.10</version>
<configuration>
<pactDirectory>../pacts</pactDirectory>
<pactBrokerUrl>http://localhost:1234</pactBrokerUrl>
<projectVersion>${project.version}</projectVersion>
</configuration>
</plugin>

The problem is you are placing your request body in the response. Your pact should look like:
#Pact(state = "provider accets submit contact form", provider = "formservice", consumer = "formclient")
public RequestResponsePact submitFormPact(PactDslWithProvider builder) {
return builder
.given("provider accetps form submit")
.uponReceiving("a request to POST form")
.path("/api/form/submit")
.method("POST")
.body(new PactDslJsonBody()
.stringType("message", "TestMessage"))
.willRespondWith()
.status(200)
.matchHeader("Content-Type", "application/json;charset=UTF-8")
.body(new PactDslJsonBody()
.booleanType("sucess", true))
.toPact();
}

Related

Spring Reactive WebClient is not calling another service

I have 2 Spring Boot microservices. Microservice (B) calls a reactive api exposed by Microservice (A).
Microservice (A) RestController code :
#RestController
#RequestMapping(value = "/documents")
public class ElasticDocumentController {
private static final Logger LOG = LoggerFactory.getLogger(ElasticDocumentController.class);
private final ElasticQueryService elasticQueryService;
public ElasticDocumentController(ElasticQueryService queryService) {
this.elasticQueryService = queryService;
}
#GetMapping(value = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ElasticQueryServiceResponseModel> getAllDocuments() {
Flux<ElasticQueryServiceResponseModel> response = elasticQueryService.getAllDocuments();
response = response.log();
LOG.info("Returning from query reactive service for all documents");
return response;
}
}
When I call the getAllDocuments() api from Postman, I can see the documents scrolling in the output cosole. So Microservice (A) is correct.
But when I call the api from Microservice (B), I cannot retrieve any documents. Microservice (B) cannot communicate with Microservice (A).
Microservice (B) Service code :
#Service
public class TwitterElasticQueryWebClient implements ElasticQueryWebClient {
private static final Logger LOG = LoggerFactory.getLogger(TwitterElasticQueryWebClient.class);
private final WebClient.Builder webClientBuilder;
private final ElasticQueryWebClientConfigData elasticQueryWebClientConfigData;
public TwitterElasticQueryWebClient(
#Qualifier("webClientBuilder") WebClient.Builder clientBuilder,
ElasticQueryWebClientConfigData configData
) {
this.webClientBuilder = clientBuilder;
this.elasticQueryWebClientConfigData = configData;
}
#Override
public Flux<ElasticQueryWebClientResponseModel> getAllData() {
LOG.info("Querying all data");
return webClientBuilder
.build()
.get()
.uri("/")
.accept(MediaType.valueOf(elasticQueryWebClientConfigData.getQuery().getAccept()))
.retrieve()
.bodyToFlux(ElasticQueryWebClientResponseModel.class);
}
}
Microservice (B) config code :
#Configuration
public class WebClientConfig {
private final ElasticQueryWebClientConfigData.WebClient webClientConfig;
public WebClientConfig(ElasticQueryWebClientConfigData webClientConfigData) {
this.webClientConfig = webClientConfigData.getWebClient();
}
#Bean("webClientBuilder")
WebClient.Builder webClientBuilder() {
return WebClient.builder()
.baseUrl(webClientConfig.getBaseUrl())
.defaultHeader(HttpHeaders.CONTENT_TYPE, webClientConfig.getContentType())
.defaultHeader(HttpHeaders.ACCEPT, webClientConfig.getAcceptType())
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(getTcpClient())))
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(webClientConfig.getMaxInMemorySize()));
}
private TcpClient getTcpClient() {
return TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webClientConfig.getConnectTimeoutMs())
.doOnConnected(connection -> {
connection.addHandlerLast(new ReadTimeoutHandler(webClientConfig.getReadTimeoutMs(), TimeUnit.MILLISECONDS));
connection.addHandlerLast(new WriteTimeoutHandler(webClientConfig.getWriteTimeoutMs(), TimeUnit.MILLISECONDS));
});
}
}
Microservice (B) application.yml :
elastic-query-web-client:
webclient:
connect-timeout-ms: 10000
read-timeout-ms: 10000
write-timeout-ms: 10000
max-in-memory-size: 10485760 # 10MB
content-type: 'application/json'
accept-type: 'text/event-stream'
base-url: 'http://localhost:8183/reactive-elastic-query-service/documents'
query:
method: POST
uri: "/get-doc-by-text"
accept: ${elastic-query-web-client.webclient.accept-type}
server:
port: 8184
spring:
webflux:
base-path: /reactive-elastic-query-web-client
thymeleaf:
cache: false
reactive:
max-chunk-size: 8192
codec:
max-in-memory-size: 25MB
Microservice (B) controller :
#Controller
public class QueryController {
private static final Logger LOG = LoggerFactory.getLogger(QueryController.class);
private final ElasticQueryWebClient elasticQueryWebClient;
public QueryController(ElasticQueryWebClient webClient) {
this.elasticQueryWebClient = webClient;
}
#GetMapping("/all")
public String queryAll(Model model) {
Flux<ElasticQueryWebClientResponseModel> responseModels = elasticQueryWebClient.getAllData();
responseModels = responseModels.log();
IReactiveDataDriverContextVariable reactiveData = new ReactiveDataDriverContextVariable(responseModels, 1);
model.addAttribute("elasticQueryWebClientResponseModels", reactiveData);
model.addAttribute("searchText", "");
model.addAttribute("elasticQueryWebClientRequestModel", ElasticQueryWebClientRequestModel.builder().build());
LOG.info("Returning from reactive client controller for all data");
return "home";
}
}
There are no exceptions in the output consoles.
I don't see what I am missing here.

Assertion error expected 500 but got 400 Junit testing

Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/

Micronaut HttpClients exchange body is always null

I have setup a simple test Controller:
#Controller("/test")
public class SampleController {
#Get(value = "1", produces = MediaType.TEXT_PLAIN)
public String helloWorld1() {
return "Hello, World!";
}
#Get(value = "2", produces = MediaType.TEXT_PLAIN)
public HttpResponse<String> helloWorld2() {
return HttpResponse.ok("Hello, World!");
}
}
And I am using the low-level HTTPClient in my Unit-Tests, which looks like this:
#MicronautTest
public class SampleControllerTest {
#Inject
EmbeddedServer server;
#Inject
#Client("/test")
HttpClient client;
#Test
void shouldReturnHelloWorld1_1() {
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET("/1").accept(
MediaType.TEXT_PLAIN));
assertEquals(200, response.code());
assertEquals("Hello, World!", response.body());
}
#Test
void shouldReturnHelloWorld1_2() {
String response = client.toBlocking().retrieve(HttpRequest.GET("/1").accept(MediaType.TEXT_PLAIN));
assertEquals("Hello, World!", response);
}
#Test
void shouldReturnHelloWorld2() {
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET("/2").accept(
MediaType.TEXT_PLAIN));
assertEquals(200, response.code());
assertEquals("Hello, World!", response.body());
}
}
From my understanding the response body should never be null, however it is for the tests shouldReturnHelloWorld2 and shouldReturnHelloWorld1_1 - so it is always null when HttpClient.exchange() is used.
In my opinion this seems to be bug or is here any issue?
You can check the whole code and run the tests yourself by cloning my sample repository: https://github.com/tobi6112/micronaut-httpclient-issue
Update:
Just noticed that the tests work as expected with
HttpResponse<String> response = client.toBlocking()
.exchange(HttpRequest.GET("/2").accept(MediaType.TEXT_PLAIN), String.class);
In my case these two options work:
final var result = client.toBlocking().exchange(HttpRequest.GET(url).accept(MediaType.APPLICATION_JSON), String.class);
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET(url).accept(MediaType.APPLICATION_JSON), String.class);

Spring RestTemplate with rootUri return URI is not absolute error

I have a RestTemplate that I build it with RestTemplateBuilder. I set the rootUri for builder. In below method (updateState1) sometimes I got the "URI is not absolute" error. For example when I called this method concurrently for 2 times I often got 1 error.
EDIT and Solution:
I use this RestTemplate in service task of camunda process. I launch this project in kubernetes container that has different timezone with the oracle database. When I add timezone variable every things work fine.
Spring boot version: 2.1.1.RELEASE
Here is my code:
#Component
#Slf4j
public class CoreServiceClient {
private RestTemplate restTemplate;
private static final String root = "http://localhost:8080/test/api/";
public CoreServiceClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder.rootUri(root).build();
}
public void updateState1(UpdateParam updateParam) {
HttpHeaders headers = generateHeader();
UpdateRequest updateRequest = new UpdateRequest(updateParam.getState());
HttpEntity<UpdateRequest> httpEntity = new HttpEntity<>(updateRequest, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/food/{id}/state",
HttpMethod.PUT, httpEntity, String.class, updateParam.getId());
}
public void updateState2(String id) {
HttpHeaders headers = generateHeader();
UpdateRequest updateRequest = new UpdateRequest("done");
HttpEntity<UpdateRequest> httpEntity = new HttpEntity<>(updateRequest, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/food/{id}/state",
HttpMethod.PUT, httpEntity, String.class, id);
}
}
cuase (stacktrace):
Caused by: java.lang.IllegalArgumentException: URI is not absolute
at java.net.URI.toURL(URI.java:1088)
at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:145)
at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:87)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:669)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:578)
at com.test.client.CoreServiceClient.updateState(CoreServiceClient.java:39)
at sun.reflect.GeneratedMethodAccessor263.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.camunda.bpm.engine.impl.javax.el.BeanELResolver.invoke(BeanELResolver.java:479)
... 85 more
Remove / in root:
private static final String root = "http://localhost:8080/test/api";
RestTemplate accepts uriTemplate as long as they start with / so your root should be without it. if it doesn't start with / it will consider it as a full URL
I try with the same code. I do not get this error.
Spring boot version: 1.5.0.RELEASE
Instead of POST, I tried with a GET API with same URL pattern.
The / at the end of the path does not matter.
#Component
public class CoreServiceClient {
private RestTemplate restTemplate;
private static final Logger LOGGER = LoggerFactory.getLogger(CoreServiceClient.class);
private static final String root = "http://localhost:8080/test/api/";
public CoreServiceClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder.rootUri(root).build();
}
public void updateState(String id) {
try {
ResponseEntity<String> response =
restTemplate.exchange("/food/{id}/state", HttpMethod.GET, null, String.class, id);
LOGGER.info("Resp: {}", response.getStatusCode());
LOGGER.info("Resp: {}", response.getBody());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
I added a dummy controller with the same path:
#RestController
#RequestMapping("/test/api")
public class FooController {
#GetMapping("/food/{id}/state")
public ResponseEntity<String> fooState(#PathVariable String id) {
return new ResponseEntity<String>("EATING", HttpStatus.OK);
}
}
To test, I added another controller:
#RestController
#RequestMapping("/client")
public class CoreServiceClientController {
#Autowired
private CoreServiceClient client;
#GetMapping
public ResponseEntity<String> goGet() {
client.updateState("1001");
return new ResponseEntity<>("HELLO", HttpStatus.OK);
}
}
Everything works fine for me.
Log:
2019-01-15 23:23:19.870 INFO 22570 --- [nio-8080-exec-1] com.example.demo001.CoreServiceClient : Resp: 200
2019-01-15 23:23:19.871 INFO 22570 --- [nio-8080-exec-1] com.example.demo001.CoreServiceClient : Resp: EATING

Bad request error unit test multipart spring rest

I have the next unit test define to test a controller which is used to upload files:
public class PhenotypeControllerTest extends BaseControllerTest{
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Before
public void setup() throws Exception {
super.setup();
}
#Test
public void loadPhenotype_success() throws Exception{
//mock uuid generation
UUID idFile = UUID.randomUUID();
//Generate the response
ResponseLoad resp = new ResponseLoad();
resp.setFileIdentifier(idFile);
resp.setStatus(Status.FINISHED);
resp.setDescription(null);
MockMultipartFile phenoFile = new MockMultipartFile("size_trans_20160419_KM2.txt","size_trans_20160419_KM2.txt", ContentType.TEXT_PLAIN.toString(), new FileInputStream("src/test/resources/size_trans_20160419_KM2.txt"));
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/phenotypes/load")
.file(phenoFile))
.andExpect(status().isOk())
.andExpect(content().contentType(this.contentType))
.andExpect(content().json(json(resp)));
}
}
The super class of the test contains the annotations:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
#TestPropertySource(locations="classpath:application.test.properties")
public abstract class BaseControllerTest {
protected MockMvc mockMvc;
#SuppressWarnings("rawtypes")
protected HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
protected WebApplicationContext webApplicationContext;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#SuppressWarnings("unchecked")
protected String json(Object o) throws IOException {
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(
o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
When I run test I get an 400 error but other tests which uses a non multipart request works fine.The controller method is like:
#ApiOperation(value = "Load Phenotype File", nickname = "loadPhenotype",
tags = {"Phenotypes"} )
#ApiResponses({
#ApiResponse(code = 200, message = "Nice!", response = Response.class),
#ApiResponse(code = 507, message = "Error uploading files")
})
#PostMapping(value="/phenotypes/load", produces = "application/json")
public ResponseEntity<ResponseLoad> uploadPhenotype(
#ApiParam(value="Phenotype File", required=true)
#RequestPart(required = true) MultipartFile file){
//1. Validate parameters
ResponseLoad response = new ResponseLoad();
response.setStatus(Status.FINISHED);
//2. Copy file to /tmp/SNPaware/phenotypes/tmp/<UUID>.pheno
response.setFileIdentifier(UUID.randomUUID());
logger.info("Storage phenotype file with identifier "+response.getFileIdentifier());
storageService.store(file, "tmp/"+response.getFileIdentifier()+".pheno");
return ResponseEntity.ok(response);
}
}
And it works correctly when I send a request to the rest api like this:
curl -X POST --header 'Content-Type: multipart/form-data' --header
'Accept: application/json' {"type":"formData"}
'http://hippo:9087/phenotypes/load'
Why I am receiving a 400 in the test? Am I missing some configuration on the test?
The problem was in the definition of the multipartFile in the test. The original name should match the name of the parameter in the controller, in this case file.
This definition solve the problem:
MockMultipartFile phenoFile = new MockMultipartFile("file", "size_trans_20160419_KM2.txt", ContentType.TEXT_PLAIN.toString(), new FileInputStream("src/test/resources/size_trans_20160419_KM2.txt"));

Categories

Resources