No consistent date format between the real application, contract tests and generated stub mappings
Hello,
I'm using Spring Boot 2.6.3 and Spring Cloud Contract 3.1.0 and Jackson 2.11.3 when writing contract ttests. This issue might be possibly related to the Maven plugin.
I am having troubles when dealing with java.util.Date objects when writing contract tests using your wonderful library.
First of all, I noticed that the default documented setup withRestAssuredWebTestClient.standaloneSetup(controller) does not use the Jackson Object Mapper of the application context. I was unpleasantly surprised to find out that the real application was generating java.util.Date objects using the ISO 8601 offset format (2022-03-17T10:34:11.815+00:00) but the application (context) used by the tests was generating the date as timestamp format (1641551651813). I would say that here, when dealing with dates, there is a big possibility of not being aligned with what the real application would produce. I did not test the behaviour with the new Java 8 date time classes.
Please note that the real application has no additional Jackson or JSON configuration for Spring Boot.
I was able to overcome the issue by using something along the lines of manually injecting an ObjectMapper, creating an HTTP message converter and setting it on an instance of MockMvc. Maybe the documentation should stress more that the MockMvc configuration does not necessarily match the real one or give more details about how to configure it.
@SpringBootTest(classes = {MyConfiguration.class, ActorsController.class})
@AutoConfigureJson
@AutoConfigureJsonTesters
public class BaseContractTestClass {
@Autowired
private ActorsController actorsController;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
public void setup() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new
MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(actorsController)
.setMessageConverters(mappingJackson2HttpMessageConverter)
.build();
RestAssuredMockMvc.mockMvc(mockMvc);
}
}
In this way I was able to assert in a contract file that the date format matches a predefined format iso_8601_with_offset using dynamic body matchers in a yml file.
But then, when running the tests on the client consumer side, I noticed that the JSON which the client sees has the dates still in the timestamp format. I checked the generated stubs and their mappings and, indeed, the dates are not using the iso_8601_with_offset format. I don't understand why and I could not find where the problem comes from.
Once again, I would like to point out that there is no special JSON Spring Boot configuration in the application, just the default one, as far as I'm aware.
Any ideas why and how to get the dates in the same format, in the contract tests on the producer and the generated stubs used by the consumer?
Many thanks, Virgiliu
Jackson jackson-datatype-jdk8 and jackson-datatype-jsr310 are transitive dependencies from spring-boot-starter-json.
AFAIR the best way would be to pass the web application context via RestAssuredMockMvc.webAppContextSetup. That way things should be resolved from application context instead of passing them manually
AFAIR the best way would be to pass the web application context via
RestAssuredMockMvc.webAppContextSetup. That way things should be resolved from application context instead of passing them manually
In the end, i simplified the base class like this:
@WebMvcTest
@TestPropertySource("/application-test.properties")
@Import(xxx.class)
public class BaseContractTestClass {
@Autowired
private MockMvc mockMvc;
@BeforeEach
public void setup() {
RestAssuredMockMvc.mockMvc(mockMvc);
}
}
But that was not the issue that I was referring to.
The problem is in the JSON stubs which are generated as a jar file for the clients, more precisely the JSON response body under the mappings folder. There the java.util.Date objects are serialized as timestamps, which is not consistent with the format used by the server (controllers), which is the iso_8601_with_offset format. From what I've seen, these are the responses that the client will get from the Wiremock server and they do not match the actual responses received by a client on the real server.
Can you show the contract, the generated test and the WireMock mapping?
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Here is the contract file:
name: search actors using criteria
request:
method: GET
url: /secure/v1.0/actors
queryParameters:
uuId: nsupuser
response:
status: 200
body:
- id: 1
uuId: nsupuser
lastReactivationDate: 2022-01-07T10:34:11.813+00:00
recUserId: hodor
recDate: 2022-01-07T10:34:11.815+00:00
modUserId: hodor
modDate: 2022-03-10T13:45:44.097+00:00
headers:
Content-Type: application/json
matchers:
body:
- path: $.[0].lastReactivationDate
type: by_regex
predefined: iso_8601_with_offset
- path: $.[0].recDate
type: by_regex
predefined: iso_8601_with_offset
- path: $.[0].modDate
type: by_regex
predefined: iso_8601_with_offset
here is the generated producer test:
import identity.domain.rest.server.IdentityRestBaseContractTestClass;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import io.restassured.response.ResponseOptions;
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*;
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
@SuppressWarnings("rawtypes")
public class ActorsTest extends IdentityRestBaseContractTestClass {
@Test
public void validate_search_actors_using_criteria() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.queryParam("uuId","nsupuser")
.get("/secure/v1.0/actors");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo("application/json");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).array().contains("['id']").isEqualTo(1);
assertThatJson(parsedJson).array().contains("['uuId']").isEqualTo("nsupuser");
assertThatJson(parsedJson).array().contains("['recUserId']").isEqualTo("hodor");
assertThatJson(parsedJson).array().contains("['modUserId']").isEqualTo("hodor");
// and:
assertThat(parsedJson.read("$.[0].lastReactivationDate", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?(Z|[+-][01]\\d:[0-5]\\d)");
assertThat(parsedJson.read("$.[0].recDate", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?(Z|[+-][01]\\d:[0-5]\\d)");
assertThat(parsedJson.read("$.[0].modDate", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?(Z|[+-][01]\\d:[0-5]\\d)");
}
}
and the generated json stub:
{
"id" : "114d2908-ecd5-4d51-af4c-3e4ffb360fd8",
"request" : {
"urlPath" : "/secure/v1.0/actors",
"method" : "GET",
"queryParameters" : {
"uuId" : {
"equalTo" : "nsupuser"
}
}
},
"response" : {
"status" : 200,
"body" : "[{\"id\":1,\"uuId\":\"nsupuser\",\"lastReactivationDate\":1641551651813,\"recUserId\":\"hodor\",\"recDate\":1641551651815,\"modUserId\":\"hodor\",\"modDate\":1646919944097}]",
"headers" : {
"Content-Type" : "application/json"
},
"transformers" : [ "response-template", "spring-cloud-contract" ]
},
"uuid" : "114d2908-ecd5-4d51-af4c-3e4ffb360fd8"
}
is anybody taking a look at this? it's almost two months since the last update.
I didn't have time but if you're willing to help PRs are more than welcome!
Is this still a problem? Can we ask for a project that replicates this issue?
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
I'm facing an issue like this for an upgrade feature and I'm able to fix this by spying jackson bean like this :
@SpyBean
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
@BeforeEach
void setup() {
RestAssuredMockMvc.standaloneSetup(MockMvcBuilders.webAppContextSetup(applicationContext));
}