Spring Cloud Contract Consumer Test Issue - spring-cloud-contract

I am testing the consumer side of the spring cloud contract.
The provider is here: https://github.com/pkid/spring-cloud-contract-with-surefire.
The stubs jar generated from the provider is here: https://github.com/pkid/spring-cloud-contract-with-surefire-consumer/blob/master/sample-repo-service-1.0.0-SNAPSHOT-stubs.jar
When I run the consumer test(source is here: https://github.com/pkid/spring-cloud-contract-with-surefire-consumer):
#Test
public void shouldGiveFreeSubscriptionForFriends() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/greeting")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string("{\"id\":1,\"content\":\"Hello, World!\"}"));
}
When I do "mvn test", I can see that the stubs jar is correctly found and unpacked. However I got the error that the endpoint 2 "/greeting" does not exist(404).
Could you please help me? Thank you!

You are using mockMvc to connect to a WireMock instance. That won't work. Change mockMvc on the consumer side to a restTemplate
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
#AutoConfigureJsonTesters
#DirtiesContext
#AutoConfigureStubRunner(ids = {"com.sap.ngp.test:sample-repo-service:+:stubs:8080"}, workOffline = true)
public class ConsumerTest {
#Test
public void shouldGiveFreeSubscriptionForFriends() throws Exception {
ResponseEntity<String> result = new TestRestTemplate().exchange(RequestEntity
.get(URI.create("http://localhost:8080/greeting"))
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build(), String.class);
BDDAssertions.then(result.getStatusCode().value()).isEqualTo(200);
BDDAssertions.then(result.getBody()).isEqualTo("{\"content\":\"Hello, World!\"}");
}
}
Please read what mock mvc is for in the docs http://docs.spring.io/spring-security/site/docs/current/reference/html/test-mockmvc.html

Related

contractVerifierMessaging.receive is null

I'm setting up contract tests for Kafka messaging with Test Containers in a way described in spring-cloud-contract-samples/producer_kafka_middleware/. Works good with Embedded Kafka but not with TestContainers.
When I try to run the generated ContractVerifierTest:
public void validate_shouldProduceKafkaMessage() throws Exception {
// when:
triggerMessageSent();
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("kafka-messages",
contract(this, "shouldProduceKafkaMessage.yml"));
Cannot invoke "org.springframework.messaging.Message.getPayload()" because "receive" is null
is thrown
Kafka container is running, the topic is created. When debugging receive method I see the message is null in the message(destination);
Contract itself:
label("triggerMessage")
input {
triggeredBy("triggerMessageSent()")
}
outputMessage {
sentTo "kafka-messages"
body(file("kafkaMessage.json"))
Base test configuration:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestConfig.class, ServiceApplication.class})
#Testcontainers
#AutoConfigureMessageVerifier
#ActiveProfiles("test")
public abstract class BaseClass {
What am I missing? Maybe a point of communication between the container and ContractVerifierMessage methods?
Resolved the issue by adding a specific topic name to listen() method in KafkaMessageVerifier implementation class.
So instead of #KafkaListener(id = "listener", topicPattern = ".*"), it works with:
#KafkaListener(topics = {"my-messages-topic"})
public void listen(ConsumerRecord payload, #Header(KafkaHeaders.RECEIVED_TOPIC)

Setting custom date formats through Jackson2ObjectMapperBuilder causing request processing to continue after exception

I have a MockMvc test for testing that a JSON payload to a controller is validated and a HTTP 400 (bad request) is rendered for org.springframework.data.mapping.PropertyReferenceException and org.springframework.http.converter.HttpMessageConversionException.
The respective exception handlers are implemented as follows.
#ControllerAdvice
public class LocalExceptionHandler {
#ExceptionHandler(PropertyReferenceException.class)
public ResponseEntity<Object> handlePropertyReferenceException(PropertyReferenceException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(HttpMessageConversionException.class)
public ResponseEntity<Object> handleHttpMessageConversionException(HttpMessageConversionException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
I'm using a Spock specification for implementing the test. The setup is as follows.
MockMvc mvc
public JsonSerializer[] buildJsonSerializers() {
return new JsonSerializer[]{new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)),
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT))};
}
Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.simpleDateFormat(DATE_TIME_FORMAT);
builder.serializers(buildJsonSerializers());
};
}
protected ObjectMapper buildObjectMapper() {
def objectMapperBuilder = new Jackson2ObjectMapperBuilder()
jsonCustomizer().customize(objectMapperBuilder)
objectMapperBuilder.modules(new MoneyModule()
.withMonetaryAmount(Money::of)
.withAmountFieldName("number")
.withFormattedFieldName("pretty"))
objectMapperBuilder.build()
}
def setup() {
ObjectMapper mapper = buildObjectMapper()
def mockMvcBuilder = MockMvcBuilders
.standaloneSetup(controller)
.setControllerAdvice(LocalExceptionHandler.class)
.setMessageConverters([new MappingJackson2HttpMessageConverter(mapper)]
.toArray(new HttpMessageConverter[1]))
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
mvc = mockMvcBuilder.build()
}
So the above setup just sets the date format through a customizer and then builds the object mapper using the Jackson2ObjectMapperBuilder.
The problem with that setup is that the builder is causing an object mapper configuration that results in a weird MockMvc behaviour.
When posting a bad request to a controller, a proper Exception is thrown and handled by one of the above exception handlers but request processing is not stopped and the controller method is invoked.
When running the production code (as Spring Boot application) error handling is just fine resulting with a HTTP 400.
Just by removing the builder and mimicking just the configuration desired for the test (which is a proper date time format) the test works as expected and request processing is stopped after exception handling.
So basically instead of using the builder I do
def mapper = new ObjectMapper()
mapper.registerModule(new MoneyModule()
.withMonetaryAmount(Money::of)
.withAmountFieldName("number")
.withFormattedFieldName("pretty"))
SimpleModule serializerModule = new SimpleModule()
Arrays.asList(buildJsonSerializers())
.forEach({ s -> serializerModule.addSerializer(s.handledType(), s) })
mapper.registerModule(serializerModule)
So it really looks like the builder is adding some configuration that MockMvc doesn't really deal with properly.
Would appreciate hints on resolving this.

Using service in Pact consumer test with Java EE

I'd like to implement a Pact consumer test in our Java EE application. This test shall invoke a consumer service method which would trigger the actual REST call.
Here's the Pact test so far:
#ExtendWith(PactConsumerTestExt.class)
#PactTestFor(providerName = "my-service")
public class MyServiceConsumerTest {
#Inject
private MyService myService;
#Pact(consumer = "myConsumer")
public RequestResponsePact mail(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", ContentType.getJSON().asString());
PactDslJsonBody jsonBody = new PactDslJsonBody()
.stringValue("emailAddress", "foo#bar.com")
.stringValue("subject", "Test subject")
.stringValue("content", "Test content")
.asBody();
return builder
.given("DEFAULT_STATE")
.uponReceiving("request for sending a mail")
.path("/mail")
.method("POST")
.headers(headers)
.body(jsonBody)
.willRespondWith()
.status(Response.Status.OK.getStatusCode())
.toPact();
}
#Test
#PactTestFor(pactMethod = "mail")
public void sendMail() {
MailNotification mailNotification = MailNotification.builder()
.emailAddress("foo#bar.com")
.subject("Test subject")
.content("Test content")
.build();
myService.sendNotification(mailNotification);
}
}
The interesting part is this line:
myService.sendNotification(mailNotification);
As I'm running a consumer unit test, the injection of MyService does not work, i.e. results in myService being null. Moreover I think it would be necessary to tell the service to send its request against the Pact mock serveR?
Of course I could just fire the final REST request in the test but that would ignore the service logic.
I guess I'm missing something here?
Yes, you should hit the mock server in the #PactVerification test. Don't fire without the actual application code, it makes a few sense in case of future changes. Tests should fail if you change an HTTP property of that request

SpringWebMvcTest - Test Requestbody using #Valid and custom validation

I am trying to test my controller endpoint and my requestbody annotated with #Valid annotation. My Testclass looks like the follow:
#RunWith(SpringRunner.class)
#WebMvcTest(value = BalanceInquiryController.class, secure = false)
public class BalanceInquiryControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BalanceInquiryController balanceInquiryController;
#Test
public void testGetBalanceInquiry() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/com/balanceInquiry")
.accept(MediaType.APPLICATION_JSON)
.content("{\"comGiftCard\":{\"cardNumber\":\"1234567890\",\"pinNumber\":\"0123\"},\"comMerchant\":\"MERCHANT1\"}")
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());
}
}
My Controller - #PostMapping looks like that:
#PostMapping(value = "/com/balanceInquiry")
public ResponseEntity<?> getBalanceInquiry(#Valid #RequestBody BalanceInquiryModel balanceInquiry, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity<String>("Validation error", HttpStatus.BAD_REQUEST);
}
//do any stuff...
return new ResponseEntity<BalanceInquiryResponse>(balanceInquiryResponse, HttpStatus.OK);
}
My BalanceInquiryModel is annotated with #Valid and has some hibernate and custom validations behind. Those validations are all ok and already unit tested.
What I like to test is my endpoint where I send a valid json request body expecting a 200 response and also an invalid json request body expecting a 400 response validated by the set #Valid implementation.
For example an unvalid call is to send no pinNumber or length < 4.
I have read some threads and some uses MockMvcBuilders.standaloneSetup() to mock the full controller. But I wont do a full integration test.
Not quite sure how to go on with this situation and if I should go on.
P.S.: At the moment I get always a 200 response no matter if the validation should give an error or not.
Here a gist for more code and the validation classes/models.
Here's one of my example I work on my project
hope it help you out:
I have a global exception handler to handler my MethodArgumentNotValidException and throw it
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> createUser(#Valid #RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<User>(savedUser, HttpStatus.CREATED);
}
public void testAdduser() throws Exception{
final User request = new User();
request.setFirstName("Test");
request.setLastName("some description");
mockMvc.perform(post(END_POINT+"/add")
.contentType(MediaType.APPLICATION_JSON)
.content(stringify(request))
).andDo(print()).andExpect(status().isUnprocessableEntity())
;
}
private String stringify(Object object) throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(object);
}
Update:
I think your main problem is that you are using #WebMvcTest in stead of #SpringBootTest.
the different between 2 of them is that:
#SpringBootTest annotation will loads complete application and injects all the beans which is can be slow.
#WebMvcTest - for testing the controller layer. it doesn't inject other bean beside the #RestController
so if you are just testing just pure controller to see u can reach the endpont then you can just use #WebMvcTest which will make your test run faster.
but in your case, you want it to run the spring validation, you will need to use #SpringBootTest
for detailed: https://spring.io/guides/gs/testing-web/

FeignClients get published as REST endpoints in spring cloud application

I've got REST FeignClient defined in my application:
#FeignClient(name = "gateway", configuration = FeignAuthConfig.class)
public interface AccountsClient extends Accounts {
}
I share endpoint interface between server and client:
#RequestMapping(API_PATH)
public interface Accounts {
#PostMapping(path = "/register",
produces = APPLICATION_JSON_VALUE,
consumes = APPLICATION_JSON_VALUE)
ResponseEntity<?> registerAccount(#RequestBody ManagedPassUserVM managedUserDTO)
throws EmailAlreadyInUseException, UsernameAlreadyInUseException, URISyntaxException;
}
Everythng works fine except that my FeignClient definition in my client application also got registered as independent REST endpoint.
At the moment I try to prevent this behavior using filter which returns 404 status code for FeignClinet client mappings in my client application. However this workeraund seems very inelegant.
Is there another way how to prevent feign clients registering as separate REST endpoints?
It is a known limitation of Spring Cloud's feign support. By adding #RequestMapping to the interface, Spring MVC (not Spring Cloud) assumes you want as an endpoint. #RequestMapping on Feign interfaces is not currently supported.
I've used workaround for this faulty Spring Framework behavior:
#Configuration
#ConditionalOnClass({Feign.class})
public class FeignMappingDefaultConfiguration {
#Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignFilterRequestMappingHandlerMapping();
}
};
}
private static class FeignFilterRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, FeignClient.class) == null);
}
}
}
I found it in SpringCloud issue

Resources