json - Testing an isolated custom JsonDeserializer in Java -


so little program i'm writing i'm looking parse twitter's tweet stream. im using gson library works nice. gson couldn't parse twitters created_at datetime field, had write custom jsondserializer needs registered parser through gsonbuilderas follows:

new gsonbuilder().registertypeadatapter(datetime.class, <mycustomdeserializertype>) 

now deserializer works well, , able parse twitter's stream.

however, i'm trying cover of program unit tests, custom deserializer should included.

since unit test nicely isolated test, not want register gson object after parse json string. do want create instance of deserializer , pass generic string representing datetime, test deserializer without being integrated else.

the signature of deserialize method of jsondeserializer follows:

deserialize(jsonelement jsonelement, type type, jsondeserializationcontext jsondeserializationcontext) 

let's want parse following data: 'mon mar 27 14:09:47 +0000 2017'. how have transform input data in order correctly test deserializer.

i'm not looking code parses date, have part covered. i'm asking how can meet deserialize method's signature can simulate it's use in gson used in.

jsonserializer , jsondeserializer tightly bound gson json tree model , specific gson configuration (de)serialization context provides set of types can (de)serialized. because of this, accomplishing unit tests jsonserializer , jsondeserializer rather hard easy.

consider following json document somewhere in src/test/resources/.../zoned-date-time.json:

"mon mar 27 14:09:47 +0000 2017" 

this valid json document, , has nothing except of single string simplicity. date/time formatter format above can implemented in java 8 follows:

final class custompatterns {      private custompatterns() {     }      private static final map<long, string> dayofweek = immutablemap.<long, string>builder()             .put(1l, "mon")             .put(2l, "tue")             .put(3l, "wed")             .put(4l, "thu")             .put(5l, "fri")             .put(6l, "sat")             .put(7l, "sun")             .build();      private static final map<long, string> monthofyear = immutablemap.<long, string>builder()             .put(1l, "jan")             .put(2l, "feb")             .put(3l, "mar")             .put(4l, "apr")             .put(5l, "may")             .put(6l, "jun")             .put(7l, "jul")             .put(8l, "aug")             .put(9l, "sep")             .put(10l, "oct")             .put(11l, "nov")             .put(12l, "dec")             .build();      static final datetimeformatter customdatetimeformatter = new datetimeformatterbuilder()             .appendtext(day_of_week, dayofweek)             .appendliteral(' ')             .appendtext(month_of_year, monthofyear)             .appendliteral(' ')             .appendvalue(day_of_month, 1, 2, not_negative)             .appendliteral(' ')             .appendvalue(hour_of_day, 2)             .appendliteral(':')             .appendvalue(minute_of_hour, 2)             .appendliteral(':')             .appendvalue(second_of_minute, 2)             .appendliteral(' ')             .appendoffset("+hhmm", "+0000")             .appendliteral(' ')             .appendvalue(year)             .toformatter();  } 

now consider following json deserializer zoneddatetime:

final class zoneddatetimejsondeserializer         implements jsondeserializer<zoneddatetime> {      private static final jsondeserializer<zoneddatetime> zoneddatetimejsondeserializer = new zoneddatetimejsondeserializer();      private zoneddatetimejsondeserializer() {     }      static jsondeserializer<zoneddatetime> getzoneddatetimejsondeserializer() {         return zoneddatetimejsondeserializer;     }      @override     public zoneddatetime deserialize(final jsonelement jsonelement, final type type, final jsondeserializationcontext context)             throws jsonparseexception {         try {             final string s = context.deserialize(jsonelement, string.class);             return zoneddatetime.parse(s, customdatetimeformatter);         } catch ( final datetimeparseexception ex ) {             throw new jsonparseexception(ex);         }     }  } 

note i'm deserialiazing string via context intention accent more complex jsondeserializer instances may depend on heavily. let's make junit tests test it:

public final class zoneddatetimejsondeserializertest {      private static final typetoken<zoneddatetime> zoneddatetimetypetoken = new typetoken<zoneddatetime>() {     };      private static final zoneddatetime expectedzoneddatetime = zoneddatetime.of(2017, 3, 27, 14, 9, 47, 0, utc);      @test     public void testdeserializeindirectlyviaautomatictypeadapterbinding()             throws ioexception {         final jsondeserializer<zoneddatetime> unit = getzoneddatetimejsondeserializer();         final gson gson = new gsonbuilder()                 .registertypeadapter(zoneddatetime.class, unit)                 .create();         try ( final jsonreader jsonreader = getpackageresourcejsonreader(zoneddatetimejsondeserializertest.class, "zoned-date-time.json") ) {             final zoneddatetime actualzoneddatetime = gson.fromjson(jsonreader, zoneddatetime.class);             assertthat(actualzoneddatetime, is(expectedzoneddatetime));         }     }      @test     public void testdeserializeindirectlyviamanualtypeadapterbinding()             throws ioexception {         final jsondeserializer<zoneddatetime> unit = getzoneddatetimejsondeserializer();         final gson gson = new gson();         final typeadapterfactory typeadapterfactory = newfactorywithmatchrawtype(zoneddatetimetypetoken, unit);         final typeadapter<zoneddatetime> datetypeadapter = typeadapterfactory.create(gson, zoneddatetimetypetoken);         try ( final jsonreader jsonreader = getpackageresourcejsonreader(zoneddatetimejsondeserializertest.class, "zoned-date-time.json") ) {             final zoneddatetime actualzoneddatetime = datetypeadapter.read(jsonreader);             assertthat(actualzoneddatetime, is(expectedzoneddatetime));         }     }      @test     public void testdeserializedirectlywithmockedcontext()             throws ioexception {         final jsondeserializer<zoneddatetime> unit = getzoneddatetimejsondeserializer();         final jsondeserializationcontext mockcontext = mock(jsondeserializationcontext.class);         when(mockcontext.deserialize(any(jsonelement.class), eq(string.class))).thenanswer(iom -> {             final jsonelement jsonelement = (jsonelement) iom.getarguments()[0];             return jsonelement.getasjsonprimitive().getasstring();         });         final jsonparser parser = new jsonparser();         try ( final jsonreader jsonreader = getpackageresourcejsonreader(zoneddatetimejsondeserializertest.class, "zoned-date-time.json") ) {             final jsonelement jsonelement = parser.parse(jsonreader);             final zoneddatetime actualzoneddatetime = unit.deserialize(jsonelement, zoneddatetime.class, mockcontext);             assertthat(actualzoneddatetime, is(expectedzoneddatetime));         }         verify(mockcontext).deserialize(any(jsonprimitive.class), eq(string.class));         verifynomoreinteractions(mockcontext);     }  } 

note each test here requires gson configuration built in order let deserialization context work, or latter must mocked. pretty test simple unit.

an alternative json tree model in gson stream-oriented type adapters that not require entire json tree constructed, can read or write directly from/to json streams making (de)serialization faster , less memory consuming. especially, simple cases trivial string<==>foobar conversions are.

final class zoneddatetimetypeadapter         extends typeadapter<zoneddatetime> {      private static final typeadapter<zoneddatetime> zoneddatetimetypeadapter = new zoneddatetimetypeadapter().nullsafe();      private zoneddatetimetypeadapter() {     }      static typeadapter<zoneddatetime> getzoneddatetimetypeadapter() {         return zoneddatetimetypeadapter;     }      @override     public void write(final jsonwriter out, final zoneddatetime zoneddatetime) {         throw new unsupportedoperationexception();     }      @override     public zoneddatetime read(final jsonreader in)             throws ioexception {         try {             final string s = in.nextstring();             return zoneddatetime.parse(s, customdatetimeformatter);         } catch ( final datetimeparseexception ex ) {             throw new jsonparseexception(ex);         }     }  } 

and here simple unit test type adapter above:

public final class zoneddatetimetypeadaptertest {      private static final zoneddatetime expectedzoneddatetime = zoneddatetime.of(2017, 3, 27, 14, 9, 47, 0, utc);      @test(expected = unsupportedoperationexception.class)     public void testwrite() {         final typeadapter<zoneddatetime> unit = getzoneddatetimetypeadapter();         unit.tojsontree(expectedzoneddatetime);     }      @test     public void testread()             throws ioexception {         final typeadapter<zoneddatetime> unit = getzoneddatetimetypeadapter();         try ( final reader reader = getpackageresourcereader(zoneddatetimetypeadaptertest.class, "zoned-date-time.json") ) {             final zoneddatetime actualzoneddatetime = unit.fromjson(reader);             assertthat(actualzoneddatetime, is(expectedzoneddatetime));         }     }  } 

for simple cases go type adapters may harder implement. refer gson unit tests more information.


Comments

Popular posts from this blog

inversion of control - Autofac named registration constructor injection -

verilog - Systemverilog dynamic casting issues -

ios - Change Storyboard View using Seague -