Как unit тест Jackson JsonSerializer и JsonDeserializer
Я написал пользовательский JsonSerializer и JsonDeserializer для своего приложения.
Теперь я хочу написать для них некоторые модульные тесты.
Как должен выглядеть чистый тестовый пример?
Есть ли там чистые примеры?
(чистый означает отсутствие зависимостей от других фреймворков или библиотек)
Ответы
Ответ 1
JsonSerializer
Пример сериализации LocalDateTime
, но это может быть заменено на требуемый тип.
@Test
public void serialises_LocalDateTime() throws JsonProcessingException, IOException {
Writer jsonWriter = new StringWriter();
JsonGenerator jsonGenerator = new JsonFactory().createGenerator(jsonWriter);
SerializerProvider serializerProvider = new ObjectMapper().getSerializerProvider();
new LocalDateTimeJsonSerializer().serialize(LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0), jsonGenerator, serializerProvider);
jsonGenerator.flush();
assertThat(jsonWriter.toString(), is(equalTo("\"2000-01-01T00:00:00\"")));
}
JsonDeserializer
В примере используется десериализация a Number
, но это может быть заменено требуемым типом.
private ObjectMapper mapper;
private CustomerNumberDeserialiser deserializer;
@Before
public void setup() {
mapper = new ObjectMapper();
deserializer = new CustomerNumberDeserialiser();
}
@Test
public void floating_point_string_deserialises_to_Double_value() {
String json = String.format("{\"value\":%s}", "\"1.1\"");
Number deserialisedNumber = deserialiseNumber(json);
assertThat(deserialisedNumber, instanceOf(Double.class));
assertThat(deserialisedNumber, is(equalTo(1.1d)));
}
@SneakyThrows({JsonParseException.class, IOException.class})
private Number deserialiseNumber(String json) {
InputStream stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
JsonParser parser = mapper.getFactory().createParser(stream);
DeserializationContext ctxt = mapper.getDeserializationContext();
parser.nextToken();
parser.nextToken();
parser.nextToken();
return deserializer.deserialize(parser, ctxt);
}
UPDATE
При обновлении до jackson 2.9.3 я получил NullPointerException
в DeserializationContext
в isEnabled(MapperFeature feature)
при десериализации строк для чисел, потому что new ObjectMapper()
инициализирует _config
до null
.
Чтобы обойти это, я использовал этот ответ SO, чтобы шпионить за final
class DeserializationContext
:
DeserializationContext ctxt = spy(mapper.getDeserializationContext());
doReturn(true).when(ctxt).isEnabled(any(MapperFeature.class));
Я считаю, что должен быть лучший способ, поэтому, пожалуйста, прокомментируйте, если у вас есть.
Ответ 2
Сепаратор может быть протестирован следующим образом:
public class CustomFooDeserializerTest {
private Foo foo;
@Before
public void setUp() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Foo.class, new CustomFooDeserializer());
objectMapper.registerModule(module);
foo = objectMapper.readValue(new File("path/to/file"), Foo.class);
}
@Test
public void shouldSetSomeProperty() {
assertThat(foo.getSomeProperty(), is("valueOfSomeProperty"));
}
}
Ответ 3
Я нашел способ unit test Deserializers, было довольно сложно разобраться. Посмотрите на мое репо https://bitbucket.org/arbeitsgruppedenktmit/de.denktmit.rest.hal
Класс unit test находится здесь:
de.denktmit.rest.hal/src/test/java/de/denktmit/rest/hal/jackson/RelationDeserializerTest.java
Тест интеграции для тестирования Deserializer в контексте можно найти здесь:
de.denktmit.rest.hal/src/test/java/de/denktmit/rest/hal/OrderResourceIntegrationTest.java
ИЗМЕНИТЬ из-за комментария
Базовый класс для настройки Mapper для простого модульного тестирования
public abstract class AbstractJackson2MarshallingTest {
protected ObjectMapper mapper;
@Before
public void setUp() {
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
protected String write(Object object) throws Exception {
Writer writer = new StringWriter();
mapper.writeValue(writer, object);
return writer.toString();
}
protected <T> T read(String source, Class<T> targetType) throws Exception {
return mapper.readValue(source, targetType);
}
protected String getPackagePath() {
return "/" + this.getClass().getPackage().getName().replace('.', '/');
}
}
Класс испытаний, содержащий модульные тесты
public class RelationDeserializerTest extends AbstractJackson2MarshallingIntegrationTest {
@Rule public ResourceFile resourceFile = new ResourceFile(getPackagePath() + "/single_valued_relation.json");
@Rule public ResourceFile resourceFile2 = new ResourceFile(getPackagePath() + "/multi_valued_relation.json");
private RelationDeserializer deserializer = new RelationDeserializer();
@Test
public void testDeserializeSingleValuedRelation() throws IOException {
resourceFile = new ResourceFile(getPackagePath() + "/single_valued_relation.json");
JsonParser parser = mapper.getFactory().createParser(resourceFile.getContent());
DeserializationContext ctxt = mapper.getDeserializationContext();
SingleValuedRelation rel = (SingleValuedRelation) deserializer.deserialize(parser, ctxt);
assertEquals(rel.getName(), "invoiceAddress");
assertEquals("invoiceAddressURL", rel.getLink().getHref());
assertEquals("linkName", rel.getLink().getName());
assertEquals("de", rel.getLink().getHreflang());
assertNull(parser.nextToken());
}
@Test
public void testDeserializeMultiValuedRelation() throws IOException {
resourceFile = new ResourceFile(getPackagePath() + "/multi_valued_relation.json");
JsonParser parser = mapper.getFactory().createParser(resourceFile.getContent());
DeserializationContext ctxt = mapper.getDeserializationContext();
MultiValuedRelation rel = (MultiValuedRelation) deserializer.deserialize(parser, ctxt);
assertEquals(rel.getName(), "images");
Iterator<Link> linkIterator = rel.getLinks().iterator();
Link link = linkIterator.next();
assertEquals("imageUrl1", link.getHref());
link = linkIterator.next();
assertEquals("imageUrl2", link.getHref());
assertNull(parser.nextToken());
}
}
Класс под тестом
public class RelationDeserializer extends StdDeserializer<Relation> {
public RelationDeserializer() {
super(Relation.class);
}
@Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
if (p.getCurrentToken() == null && p.nextToken() == null) {
String msg = getClass().getCanonicalName()
+ ": Can not deserialize without token";
throw new IOException(msg);
}
if (p.getCurrentToken() != JsonToken.START_OBJECT
&& p.getCurrentToken() != JsonToken.START_ARRAY) {
String msg = getClass().getCanonicalName()
+ ": Expected data to start with an Relation object or an array of Relation objects";
throw new IOException(msg);
}
if (p.nextToken() != JsonToken.FIELD_NAME) {
String msg = getClass().getCanonicalName()
+ ": Expected relation to be started by a field name";
throw new IOException(msg);
}
String relationName = p.getText();
JsonToken tok = p.nextToken();
Relation rel;
switch (tok) {
case START_ARRAY:
rel = createMultiValuedRelation(relationName, p);
break;
case START_OBJECT:
rel = createSingleValuedRelation(relationName, p);
break;
default:
String msg = getClass().getCanonicalName() + "Expected relation content is a single link or array of links";
throw new IOException(msg);
}
p.nextToken();
return rel;
}
private Relation createMultiValuedRelation(String relationName, JsonParser p)
throws JsonParseException, IOException {
List<Link> links = new ArrayList<Link>();
if (p.nextToken() == JsonToken.START_OBJECT) {
Iterator<DefaultLink> linkIterator = p.readValuesAs(DefaultLink.class);
while (linkIterator.hasNext()) {
links.add(linkIterator.next());
}
}
if (p.getCurrentToken() != JsonToken.END_ARRAY) {
String msg = getClass().getCanonicalName() + "Expected relation content is a single link or (possibly empty) array of links";
throw new IOException(msg);
}
return RelationFactory.createRelation(relationName, links);
}
private Relation createSingleValuedRelation(String relationName,
JsonParser p) throws JsonParseException, IOException {
return RelationFactory.createRelation(relationName, p.readValueAs(DefaultLink.class));
}
}
Надеюсь, что это поможет и с наилучшими пожеланиями
Ответ 4
Я не нашел никакого примера, но вы можете попытаться создать генератор, например:
StringWriter stringJson = new StringWriter();
JsonGenerator generator = new JsonFactory().createGenerator(stringJson);
и вы можете получить экземпляр SerializerProvider
с
new ObjectMapper().getSerializerProvider();
Ответ 5
Я начал использовать решение в ответе @RobertBain, но оно не сработало для меня, так как анализатор JSON продолжал каким-то образом терять значение. Я закончил тем, что включил Mockito и заглушил метод на JsonParser
который использует мой десериализатор, и написал мои тесты, используя это. Смотрите небольшой пример ниже:
public class ZoneDateTimeDeserializerTest {
private ZoneDateTimeDeserializer deserializer;
@Mock
private JsonParser parser;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Before
public void setUp() {
deserializer = new ZoneDateTimeDeserializer();
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
@Test
public void givenAZonedDateTimeInIsoFormat_whenTryingToDeserializeIt_itShouldBeSuccessful() {
final String date = "1996-01-29T22:40:54+02:00";
ZonedDateTime dateTime = deserialiseZoneDateTime(date);
Assert.assertNotNull(dateTime);
assertThat(dateTime, instanceOf(ZonedDateTime.class));
Assert.assertEquals(1996, dateTime.getYear());
Assert.assertEquals(Month.JANUARY, dateTime.getMonth());
Assert.assertEquals(29, dateTime.getDayOfMonth());
Assert.assertEquals(20, dateTime.getHour());
Assert.assertEquals(40, dateTime.getMinute());
Assert.assertEquals(54, dateTime.getSecond());
}
@SneakyThrows({JsonParseException.class, IOException.class})
private ZonedDateTime deserialiseZoneDateTime(String json) {
// Mock the parser so that it returns the value when getValueAsString is called on it
when(parser.getValueAsString()).thenReturn(json);
return deserializer.deserialize(parser, null);
}
}
Ответ 6
Другие ответы очень хорошо охватили сериализацию. У меня есть еще одно предложение для десериализации.
Если ваш класс домена имеет аннотацию @JsonDeserialize
, то ваш десериализатор будет выбран @JsonDeserialize
по умолчанию без необходимости явной регистрации.
@JsonDeserialize(using=ExampleDeserializer.class)
public class Example {
private String name;
private BananaStore bananaStore;
public Example(String name, BananaStore bananaStore) {
this.name = name;
this.bananaStore = bananaStore;
}
public String getName() {
return name;
}
public BananaStore getBananaStore() {
return bananaStore;
}
}
и ваш юнит-тест может стать намного короче:
public class ExampleDeserializerTest {
@Test
public void deserialize() throws IOException {
String json = "{\"name\":\"joe\",\"bananas\":16}";
Example example = new ObjectMapper().readValue(json, Example.class);
assertEquals("joe", example.getName());
assertEquals(16, example.getBananaStore().getBananaCount());
}
}