I am trying to implement Spring Cloud Contract to my project. I am following instructions from this baeldung article:
https://www.baeldung.com/spring-cloud-contract
Dependencies are added
Plugin is configured
Producer contract is defined
BaseTest is defined
Unfortunately my generated tests fails because the response (jsonBody) is "empty"
Here's a few pieces of the setup:
BaseContractTest =>
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMessageVerifier
#DirtiesContext
#AutoConfigureStubRunner(ids = "com.example:producer-service:+:stubs:8080",
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class BaseContractTest
{
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeEach
void setUp()
{
final DefaultMockMvcBuilder defaultMockMvcBuilder =
MockMvcBuilders.webAppContextSetup(webApplicationContext);
defaultMockMvcBuilder.apply(
springSecurity((request, response, chain) -> chain.doFilter(request, response)));
RestAssuredMockMvc.mockMvc(defaultMockMvcBuilder.build());
}
contract =>
Contract.make {
description "GetCustomer should return a Customer"
request {
method GET()
url value(consumer(regex('/producer-service/v1/customer/ID-\\d*-\\d*')), producer("/producer-service/customer/ID-132456-9876"))
}
response {
status OK()
body(
id: "ID-132456-9876", name: "exampleName"
)
headers {
contentType(applicationJson())
}
}
}
wiremocks mapping are properly generated (omitted for brevity)
Generated ContractTest =>
public class ContractVerifierTest extends BaseContractTest {
#Test
public void validate_shouldReturnACustomer() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/producer-service/v1/ID-132456-9876");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['id']").isEqualTo("ID-132456-9876");
assertThatJson(parsedJson).field("['name']").isEqualTo("exampleName");
}
}
when the test runs, it fails with this error:
validate_shouldReturnACustomer Time elapsed: 0.731 s <<< FAILURE!
java.lang.AssertionError:
Expecting actual not to be null
at com.example.contracts.ContractVerifierTest.validate_shouldReturnACustomer(ContractVerifierTest.java:31)
When I look up the corresponding error line, it fails on =>
assertThat(response.header("Content-Type")).matches("application/json.*");
I am a bit clueless at this point.
I tried to use the MockStandaloneApp tied to the controller (as per the link to baeldung) but that did not help.
Note that the service returns a Mono<Customer> not an actual Customer, if that changes anything.
Related
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)
I made a test that checked whether a post method from my controller does what it's supposed to. It worked great! Now I'm supposed to make a test to see whether the right message pops up when I get an 400 error for that post method.
Here's what I've got:
public void shouldReturnBadRequestExceptionWhenGivenBadArguments() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
mvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest())
.andExpect(result -> assertEquals("Email is already taken!", Objects.requireNonNull(result.getResolvedException()).getMessage()))
// .andExpect(result -> assertTrue(result.getResolvedException() instanceof BadRequestException));
}
The test gives me an error as such: Status expected:<400> but was:<200>.
Now I do understand that it just means I didn't get an error and instead the post method worked. Now the thing I don't know how to do is to get that error on purpose. Anyone know how to do this?
EDIT: was told to post the controller endpoint so here it is:
#PostMapping
public UserDTO addUser(#RequestBody CreateUserDTO newUser) {
log.info(newUser.toString());
return userService.createUser(newUser);
}
and my user was created in my setUp() (with #BeforeEach annotation) as such:
#BeforeEach
public void setUp() throws Exception {
user = new User(
"ime",
"prezime",
"imeprezime#consulteer.com",
"1234");
}
Hope this helps!
EDIT 2: added a part of service class concerning the post method:
#Override
public UserDTO createUser(CreateUserDTO newUser) {
if(userRepository.findByEmail(newUser.getEmail()).isPresent())
throw new BadRequestException("Email is already taken!");
return userMapper.convertEntityToDTO(userRepository.save(userMapper.convertCreateDTOToEntity(newUser)));
}
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.
Given the following unit test, which uses the Vertx Unit testing framework:
#RunWith(VertxUnitRunner.class)
public class VertxUnitTest {
private Vertx vertx;
#Rule
public RunTestOnContext rule = new RunTestOnContext(new VertxOptions().setClustered(false)
.setClusterManager(new HazelcastClusterManager()).setMaxEventLoopExecuteTime(2000000000000L)
.setMaxWorkerExecuteTime(60000000000000L).setBlockedThreadCheckInterval(1000000)
.setEventBusOptions(new EventBusOptions().setClustered(false).setIdleTimeout(0)));
#Before
public void setup() throws Exception {
io.vertx.core.Vertx v = rule.vertx();
vertx = Vertx.newInstance(v);
}
private class MyVerticle extends AbstractVerticle {}
#Test
public void runFlow_correctMessage_stepsCalledInCorrectOrder(TestContext context) {
Async async = context.async();
vertx.getDelegate().deployVerticle(new MyVerticle(), new DeploymentOptions().setWorker(true), c -> {
c.cause();
vertx.eventBus().<Object>send("", new JsonObject(), new DeliveryOptions(), rpl -> {
async.complete();
fail();
});
});
}
}
the call to fail() is throwing an exception to the console, but it is not actually failing the test itself, which finishes successfully and is green.
The same is true when working with Mockito. I can successfully verify the behavior of the verticle and its dependencies using mocks, but even when the Mockito assertions fail, the test itself will still pass. Calling fail on the vertx TestContext object - context.fail() - will also not fail the test.
The core issue is this: any call to fail() after async.complete() will not fail the test, only the console will show the error. But without the call to async.complete(), the code in the verticle (called upon consuming from the event bus), will not have run before the test assertions are called.
Without the call to async.complete(), the test will it appears never complete.
What is the correct approach to this?
Thanks
the correct approach is to call the TestContext.fail() method, like so:
#Test
public void runFlow_correctMessage_stepsCalledInCorrectOrder(TestContext context) {
Async async = context.async();
vertx.getDelegate().deployVerticle(new MyVerticle(), new DeploymentOptions().setWorker(true), c -> {
if(c.succeeded()) {
vertx.eventBus().<Object>send("", new JsonObject(), new DeliveryOptions(), rpl -> {
if(rpl.succeeded()) {
// make assertions based on reply contents, and then...
async.complete();
} else {
context.fail(rpl.cause());
}
});
} else {
context.fail(c.cause());
}
});
}
I am trying to write a Spring WS client using WebServiceGatewaySupport. I managed to test the client for a successful request and response. Now I wanted to write test cases for soap faults.
public class MyClient extends WebServiceGatewaySupport {
public ServiceResponse method(ServiceRequest serviceRequest) {
return (ServiceResponse) getWebServiceTemplate().marshalSendAndReceive(serviceRequest);
}
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SpringTestConfig.class)
#DirtiesContext
public class MyClientTest {
#Autowired
private MyClient myClient;
private MockWebServiceServer mockServer;
#Before
public void createServer() throws Exception {
mockServer = MockWebServiceServer.createServer(myClient);
}
}
My question is how do i stub the soap fault response in the mock server, so that my custom FaultMessageResolver will be able to unmarshall soap fault?
I tried couple of things below, but nothing worked.
// responsePayload being SoapFault wrapped in SoapEnvelope
mockServer.expect(payload(requestPayload))
.andRespond(withSoapEnvelope(responsePayload));
// tried to build error message
mockServer.expect(payload(requestPayload))
.andRespond(withError("soap fault string"));
// tried with Exception
mockServer.expect(payload(requestPayload))
.andRespond(withException(new RuntimeException));
Any help is appreciated. Thanks!
Follow Up:
Ok so, withSoapEnvelope(payload) I managed to get the controller to go to my custom MySoapFaultMessageResolver.
public class MyCustomSoapFaultMessageResolver implements FaultMessageResolver {
private Jaxb2Marshaller jaxb2Marshaller;
#Override
public void resolveFault(WebServiceMessage message) throws IOException {
if (message instanceof SoapMessage) {
SoapMessage soapMessage = (SoapMessage) message;
SoapFaultDetailElement soapFaultDetailElement = (SoapFaultDetailElement) soapMessage.getSoapBody()
.getFault()
.getFaultDetail()
.getDetailEntries()
.next();
Source source = soapFaultDetailElement.getSource();
jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.company.project.schema");
Object object = jaxb2Marshaller.unmarshal(source);
if (object instanceof CustomerAlreadyExistsFault) {
throw new CustomerAlreadyExistsException(soapMessage);
}
}
}
}
But seriously!!! I had to unmarshall every message and check the instance of it. Being a client I should be thorough with all possible exceptions of the service here, and create custom runtime exceptions and throw it from the resolver. Still at the end, its been caught in WebServiceTemplate and re thrown as just a runtime exception.
You could try with something like this:
#Test
public void yourTestMethod() // with no throw here
{
Source requestPayload = new StringSource("<your request>");
String errorMessage = "Your error message from WS";
mockWebServiceServer
.expect(payload(requestPayload))
.andRespond(withError(errorMessage));
YourRequestClass request = new YourRequestClass();
// TODO: set request properties...
try {
yourClient.callMethod(request);
}
catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(errorMessage);
}
mockWebServiceServer.verify();
}
In this part of code mockWebServiceServer represents the instance of MockWebServiceServer class.