A common struggle with unit testing is
figuring when to just assume somebody else’s code works. One such example
is serializability: for simple classes, it should “just work” so we
shouldn’t need to write a unit test for each of them. However, I still
wanted to be able to verify that all classes in certain namespaces were
marked as [Serializable]
, so I wrote the following test:
[TestCase(typeof(Money), "Solutionizing.Domain")]
[TestCase(typeof(App), "Solutionizing.Web.Models")]
public void Types_should_be_Serializable(Type sampleType, string @namespace)
{
var assembly = sampleType.Assembly;
var unserializableTypes = (
from t in assembly.GetTypes()
where t.Namespace != null && t.Namespace.StartsWith(@namespace, StringComparison.Ordinal)
where !t.IsSerializable && ShouldBeSerializable(t)
select t
).ToArray();
unserializableTypes.ShouldBeEmpty();
}
After we have a reference to the Assembly
under test, we
use a LINQ to Objects query against its types. If a type matches our
namespace filter, we make sure it’s serializable if it should be.
Finally, by using ToArray()
and ShouldBeEmpty()
we’re given a nice error message if the test fails:
TestCase 'Solutionizing.Tests.SerializabilityTests.Types_should_be_Serializable(Solutionizing.Domain.Money, Solutionizing.Domain)'
failed:
Expected: <empty>
But was: < <Solutionizing.Domain.Oops>, <Solutionizing.Domain.OopsAgain> >
SerializabilityTests.cs(29,0): at Solutionizing.Tests.SerializabilityTests.Types_should_be_Serializable(Type sampleType, String namespace)
I use a few criteria to determine if I expect the type to be
serializable:
private bool ShouldBeSerializable(Type t)
{
if (IsExempt(t))
return false;
if (t.IsAbstract && t.IsSealed) // Static class
return false;
if (t.IsInterface)
return false;
if (!t.IsPublic)
return false;
return true;
}
Other than IsExempt()
, the code should be more or less
self-explanatory. If you had never bothered to check how static classes
are represented in IL, now you know: abstract (can’t be instantiated) +
sealed (can’t be inherited). Also, note that !IsPublic
will
cover compiler-generated classes for iterators and closures that we
don’t need to serialize.
The final piece is providing a way we can exempt certain classes from
being tested:
private bool IsExempt(Type t)
{
return exemptTypes.Any(e => e.IsAssignableFrom(t));
}
private Type[] exemptTypes = new []
{
typeof(SomeClassWithDictionary), // Wrapped dictionary is not serializable
typeof(Attribute) // Metadata are never serialized
};
Of course, this isn’t a replacement for actually testing that custom
serialization works correctly for more complicated objects, particularly
if your classes may depend on others that aren’t covered by these
tests. But I have still found this test to be a useful first level of
protection.