If you do not associate the concept Boundary Test to Agile Software Development, please refer to
Robert C. Martin (a.k.a Uncle Bob) - Clean Code: A Handbook of Agile Software Craftsmanship / Chapter 8: Boundaries
Now, let go to the subject ...
The Problem
While de-serializing some POCOs using Json.NET one property, a collection, remains empty but the input JSON has the corresponding array to fill it.The property was a list of integers (List
The actual implementation was something like this:
[DataContract]
public class DummyPoco
{
private ISet<int> internalSet01;
public DummyPoco()
{
internalSet01 = new HashSet<int>();
}
[DataMember]
[JsonProperty("fakeList01")]
public List<int> FakeList01
{
get
{
return this.internalSet01.ToList();
}
set
{
var safeList = (value ?? new List<int>());
this.internalSet01 = new HashSet<int>(safeList);
}
}
[DataMember]
[JsonProperty("realList")]
public List<int> RealList { get; set; }
}
I tested my code using a classic NUnit test, without worrying about JSON serialization issues. After all tests were done (green), I took the next step expose my, carefully tested, API as a WCF REST end point. There I met reality.
Any time the client (single page application) sends to the REST end point the resource to be de-serialized as my POCO that property (FakeList01) was empty.
Other collections (like RealList) with simple property constructors have no problem. Clearly something with my 'Fake' list (internal) set was causing the problem.
{ myFakeList: [ 1, 1, 2, 2 ], myRealList: [ 1, 1, 2, 2, 3, 3 ] } de-serializes as
poco.FakeList01.Count /* 0 */;I didn't want to use a simple property and be forced to deal with duplicated data in other points of the application.
poco.RealList.Count /* 6 */;
The Solution
Reading some post in StackOverflow and the Json.NET API documentation, I found the following parameter for the JsonProperty annotation: ObjectCreationHandling. ObjectCreationHandling parameter accepts values from an homonym enumeration . That enum has three possible values Auto, Reuse, and Replace. The default is Auto.ObjectCreationHandling.Auto means
- If the property being set is null (known by calling the getter), then create a new instance and assign it (by calling the setter). Doing a 'replace' (of the property's reference).
- If the property being set is not null (also known by calling the getter), then keep this reference and and use its properties to set all values. For collection types, keep that instance and call Add to store elements. Doing a 'reuse' (of the property's reference).
ObjectCreationHandling.Replace means
- "Always ignore the actual property value and override it with a new instance".
Because my property's getter was returning a 'detached' list (internalSet01.ToList()), Json.NET was using that 'detached' list to add elements found in JSON input. And ignoring the setter. Clearly the solution was to change the Auto value for that parameter.
[DataMember]
[JsonProperty("fakeList01", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<int> FakeList01 { ... }
}
After doing that, I remembered the "Boundary Test" concept explained by Uncle Bob in his book "Clean Code".
The "missing test" for this bug was a Boundary Test, one that validates the usage of this JSON serialization library and ensures that a properly annotated class could be used without problems, no matter the actual implementation of the property's accessors.
With that test in place, I could test if changes in the Json.NET project will break my assumptions about JSON serialization and de-serialization of weird properties like the one described.
Also I added a 'negative test' by creating a similar property but leaving the ObjectCreationHandling attribute with its default value (Auto).
[DataMember]
[JsonProperty("fakeList02")]
public List<int> FakeList02
{
get
{
return this.internalSet02.ToList();
}
set
{
var safeList = (value ?? new List<int>());
this.internalSet02 = new HashSet<int>(safeList);
}
}
Notes
These technique is not considered as 'third-party library testing'. Json.NET has a vast amount of functionalities that I'm not testing. I'm only testing the part of the framework actively used.References
- .NEt Project: on Github ( https://github.com/lsolano/json_deserialization_boundary_test )
- Json.NET: Newtonsoft.Json.dll, v6.0.0.0
- Sources: Download it from NuGet or http://www.newtonsoft.com/json
- NUnit: nunit.framework.dll, v2.6.4.14350
- Sources: Download it from NuGet or http://www.nunit.org/
No comments:
Post a Comment
enter your comments here...