Kube Cloud Pt6 | Contract Testing
full course- Kube Cloud Pt6 | Contract Testing
- Kube Cloud Pt6 | Consumer Contract Tests for REST Endpoints
- Kube Cloud Pt6 | Provider Contract Test for REST Endpoints
- Kube Cloud Pt6 | Fulfill the Consumer Contract Test for REST Endpoint
- Kube Cloud Pt6 | Break the Contract from a Consumer Change
- Kube Cloud Pt6 | Synchronous Contract Testing Conclusion
We’re going to go back to cloud-application at this point and update our client to expect a field that isn’t being currently provided (message generated date). This should break our contract test and prevent us from deploying, but there’s some updates we need to make to support this.
Add the New Feature to the Consumer
Go to the cloud-application project and start a new branch
$ git checkout -b generated-date
Switched to a new branch 'generated-date'
We’re using dates, so lets make sure that we have the correct serializers in the pom.xml
<!-- dates -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
And we’re going to have to fix the logging aspect at src/main/java/com/bullyrooks/cloud_application/config/LoggingAspect.java
ObjectMapper om;
@Autowired
public LoggingAspect() {
om = new ObjectMapper()
.registerModule(new JavaTimeModule());
}
Essentially we’re going to add the java time serialization module to our json object mapper
Make a minor update to src/main/resources/application.yml
spring:
application:
name: cloud-application
jackson:
serialization:
write-dates-as-timestamps: false
This is just something I use to make sure that my timestamps are written out correctly, its not directly applicable here, but since we’re messing with java8 dates, its a safe thing to do
Now add our new field to
src/main/java/com/bullyrooks/cloud_application/controller/dto/CreateMessageResponseDTO.java
src/main/java/com/bullyrooks/cloud_application/message_generator/client/dto/MessageResponseDTO.java
src/main/java/com/bullyrooks/cloud_application/service/model/MessageModel.java
private Instant generatedDate;
Add the new date to the test data in src/test/resources/json/message-generator-response.json
"message": "All dwarfs are bastards in their father's eyes",
"generatedDate": "2022-03-07T18:37:54.124523300Z"
Now we need to make a new mapper at src/main/java/com/bullyrooks/cloud_application/message_generator/mapper/MessageGeneratorMapper.java
package com.bullyrooks.cloud_application.message_generator.mapper;
import com.bullyrooks.cloud_application.message_generator.client.dto.MessageResponseDTO;
import com.bullyrooks.cloud_application.service.model.MessageModel;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MessageGeneratorMapper {
MessageGeneratorMapper INSTANCE = Mappers.getMapper(MessageGeneratorMapper.class);
MessageModel messageResponseToMessage(@MappingTarget MessageModel in, MessageResponseDTO dto);
}
And the corresponding updates to the service class at src/main/java/com/bullyrooks/cloud_application/service/MessageService.java
messageModel = MessageGeneratorMapper.INSTANCE.messageResponseToMessage(messageModel, dto);
genMsgSuccess.increment();
log.info("retrieved message: {}", messageModel.getMessage());
} else {
//if they provided a message, return now
messageModel.setGeneratedDate(Instant.now());
}
So instead of copying fields over individually (for message
and generatedDate
) the mapper will take in the original message and then add the fields that are missing from the dto object. If we passed in a message, we want to return a new generatedDate
.
Now update the behavior test at src/test/java/com/bullyrooks/cloud_application/controller/MessageControllerTest.java
@AfterEach
void cleanup(){
//cleanup
outputDestination.clear("message.created");
}
...
@Test
void testSaveMessage() throws IOException {
Long userId = 1l;
//given
Instant testStart = Instant.now();
CreateMessageRequestDTO request = CreateMessageRequestDTO
.builder()
...
assertEquals(request.getMessage(), dto.getMessage());
assertTrue(dto.getGeneratedDate().isAfter(testStart));
...
@Test
void testGetReturnsMessageIfMissing() throws InterruptedException, IOException {
Long userId = 1l;
//given
Instant testStart = Instant.now();
CreateMessageRequestDTO request = CreateMessageRequestDTO
.builder()
...
when(messageGeneratorClient.getMessage()).thenReturn(
MessageResponseDTO.builder()
.message(faker.gameOfThrones().quote())
.generatedDate(Instant.now())
.build());
...
assertTrue(StringUtils.isNotBlank(dto.getMessage()));
assertTrue(dto.getGeneratedDate().isAfter(testStart));
I’m adding a cleanup method to the kafka testing logic because I found that if my tests were failing data from the previous test was polluting the topic for the next test. Next, I’m adding a ‘test start’ date and confirming that the message I get back from the endpoint is after that date. Additionally, I’m updating the mock so that it will return a generatedDate field.
Finally the contract test at src/test/java/com/bullyrooks/cloud_application/message_generator/client/MessageGeneratorClientTest.java
...
public void generateMessage() {
MessageResponseDTO response = messageGeneratorClient.getMessage();
assertTrue(StringUtils.isNotBlank(response.getMessage()));
assertTrue(null != response.getGeneratedDate());
}
...
No major changes here… just that I’m expecting the date that my json is now returning. Keep in mind that I have to manually update two tests which is what I warned about before. If I didn’t update the contract, my tests would pass and I would think that I’m good to deploy, but I’m not (message-generator doesn’t support this functionality yet)
Go ahead and add/commit and push this up. Go to github cloud-application repository and create a new pull request for this change. This should fail, since the provider doesn’t have this field.
Update the Provider to Fulfill the Contract
Switch back over to message-generator and add the code needed to fulfill the contract.
Add generatedDate
to MessageResponseDTO
and MessageModel
(I may have refactored the MessageService
to return a MessageModel
object that looks like this)
package com.bullyrooks.messagegenerator.service.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageModel {
private String message;
private Instant generatedDate;
}
Update MessageService
to return the generatedDate
public MessageModel getMessage(){
MessageModel model = MessageModel.builder()
.message(faker.gameOfThrones().quote())
.generatedDate(Instant.now())
.build();
return model;
}
Update MessageControllerContractIT
test so that it can fulfill the contract
@State("generator creates a message")
public void shouldReturnMessage() {
//@formatter:off
MessageModel dto = MessageModel.builder()
.message("All dwarfs are bastards in their father's eyes")
.generatedDate(Instant.parse("2022-03-07T18:37:54.124523300Z"))
.build();
Mockito.when(service.getMessage()).thenReturn(dto);
//@formatter:on
}
and update MessateControllerTest
to expect the generatedDate
field
//Verify request succeed
assertEquals(200, result.getStatusCodeValue());
MessageResponseDTO response = result.getBody();
log.info("Test message returned: {}",response.getMessage());
assertTrue(StringUtils.isNotBlank(response.getMessage()));
assertNotNull(response.getGeneratedDate());
Go ahead and push your feature branch and create the pull request in message-generator
. This should pass so you can merge. If it does, go ahead and do so
Rebuild the Consumer
Switch back over to your cloud-application pull request, click on the failed build and restart it as we did before (wait for the provider build to main to succeed first just in case, it should work without needing to wait though)
This build should now succeed and you can view the successful build in the pull request as well as the contract fulfillment via pactflow
Go ahead an merge the pull request now.
That’s it! We’re now successfully blocking breaking builds from the consumer application. We can also make a breaking change on the provider side, but its essentially the same process (i.e. remove the generatedDate that we just added) and the change will not make it through the pull request or will it actually deploy if someone forces the change to main.
0 comments on “Kube Cloud Pt6 | Break the Contract from a Consumer Change”Add yours →