Skip to content

Commit 3ecd0b1

Browse files
committed
Improve ObjectMapper configuration test coverage
1 parent cb6f7b1 commit 3ecd0b1

1 file changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package ee.bitweb.core.object_mapper;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.DeserializationFeature;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Tag;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
10+
11+
import java.time.OffsetDateTime;
12+
import java.time.ZoneOffset;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
16+
17+
/**
18+
* Integration tests for {@link ObjectMapperAutoConfiguration.Jackson2ObjectMapperCreator}.
19+
*
20+
* <p>Uses {@link ApplicationContextRunner} to create a lightweight context where no pre-existing
21+
* {@code ObjectMapper} bean is defined, allowing the {@code @ConditionalOnMissingBean(ObjectMapper.class)}
22+
* condition to be satisfied and the Creator inner class to activate.</p>
23+
*
24+
* <p>This scenario is never covered by the standard {@code @SpringBootTest} integration tests because
25+
* {@code TestSpringApplication} always provides an {@code ObjectMapper} bean via Spring Boot auto-configuration.</p>
26+
*/
27+
@Tag("integration")
28+
class Jackson2ObjectMapperCreatorIntegrationTests {
29+
30+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
31+
.withUserConfiguration(ObjectMapperAutoConfiguration.class)
32+
.withPropertyValues("ee.bitweb.core.object-mapper.auto-configuration=true");
33+
34+
@Test
35+
@DisplayName("Should load Jackson2ObjectMapperCreator inner class when no ObjectMapper exists")
36+
void shouldLoadJackson2ObjectMapperCreatorBean() {
37+
contextRunner.run(context -> {
38+
assertThat(context).hasSingleBean(ObjectMapperAutoConfiguration.Jackson2ObjectMapperCreator.class);
39+
});
40+
}
41+
42+
@Test
43+
@DisplayName("Should create ObjectMapper bean via Jackson2ObjectMapperCreator")
44+
void shouldCreateObjectMapperBean() {
45+
contextRunner.run(context -> {
46+
assertThat(context).hasSingleBean(ObjectMapper.class);
47+
assertThat(context).hasBean("objectMapper");
48+
});
49+
}
50+
51+
@Test
52+
@DisplayName("Created ObjectMapper should trim strings during deserialization")
53+
void shouldTrimStringsDuringDeserialization() {
54+
contextRunner.run(context -> {
55+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
56+
String json = "{\"value\": \" trimmed \"}";
57+
58+
TestDto result = objectMapper.readValue(json, TestDto.class);
59+
60+
assertThat(result.value).isEqualTo("trimmed");
61+
});
62+
}
63+
64+
@Test
65+
@DisplayName("Created ObjectMapper should handle Java time types with JavaTimeModule")
66+
void shouldHandleJavaTimeTypes() {
67+
contextRunner.run(context -> {
68+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
69+
String json = "{\"timestamp\": \"2024-01-15T10:30:00Z\"}";
70+
71+
TestDtoWithTime result = objectMapper.readValue(json, TestDtoWithTime.class);
72+
73+
assertThat(result.timestamp).isNotNull();
74+
assertThat(result.timestamp.getYear()).isEqualTo(2024);
75+
assertThat(result.timestamp.getMonthValue()).isEqualTo(1);
76+
assertThat(result.timestamp.getDayOfMonth()).isEqualTo(15);
77+
});
78+
}
79+
80+
@Test
81+
@DisplayName("Created ObjectMapper should have ADJUST_DATES_TO_CONTEXT_TIME_ZONE disabled")
82+
void shouldHaveAdjustDatesToContextTimeZoneDisabled() {
83+
contextRunner.run(context -> {
84+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
85+
86+
assertThat(objectMapper.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)).isFalse();
87+
});
88+
}
89+
90+
@Test
91+
@DisplayName("Created ObjectMapper should have ACCEPT_FLOAT_AS_INT disabled")
92+
void shouldHaveAcceptFloatAsIntDisabled() {
93+
contextRunner.run(context -> {
94+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
95+
96+
assertThat(objectMapper.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT)).isFalse();
97+
});
98+
}
99+
100+
@Test
101+
@DisplayName("Created ObjectMapper should reject float value for integer field")
102+
void shouldRejectFloatForIntegerField() {
103+
contextRunner.run(context -> {
104+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
105+
String json = "{\"count\": 1.5}";
106+
107+
assertThatThrownBy(() -> objectMapper.readValue(json, TestDtoWithInt.class))
108+
.isInstanceOf(JsonProcessingException.class);
109+
});
110+
}
111+
112+
@Test
113+
@DisplayName("Created ObjectMapper should preserve timezone offset in deserialized date")
114+
void shouldPreserveTimezoneInDate() {
115+
contextRunner.run(context -> {
116+
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
117+
String json = "{\"timestamp\": \"2024-01-15T10:30:00+05:00\"}";
118+
119+
TestDtoWithTime result = objectMapper.readValue(json, TestDtoWithTime.class);
120+
121+
assertThat(result.timestamp.getOffset()).isEqualTo(ZoneOffset.ofHours(5));
122+
});
123+
}
124+
125+
@Test
126+
@DisplayName("Jackson2ObjectMapperCustomizer should NOT be loaded when no pre-existing ObjectMapper exists")
127+
void shouldNotLoadJackson2ObjectMapperCustomizerBean() {
128+
contextRunner.run(context -> {
129+
assertThat(context).doesNotHaveBean("objectMapperAutoConfiguration.Jackson2ObjectMapperCustomizer");
130+
});
131+
}
132+
133+
static class TestDto {
134+
public String value;
135+
}
136+
137+
static class TestDtoWithTime {
138+
public OffsetDateTime timestamp;
139+
}
140+
141+
static class TestDtoWithInt {
142+
public Integer count;
143+
}
144+
}

0 commit comments

Comments
 (0)