37

I am using C# 2.0 with Nunit Test. I have some object that needs to be serialized. These objects are quite complex (inheritance at different levels and contains a lot of objects, events and delegates).

How can I create a Unit Test to be sure that my object is safely serializable?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Pokus
  • 11,383
  • 11
  • 37
  • 27

7 Answers7

49

Here is a generic way:

public static Stream Serialize(object source)
{
    IFormatter formatter = new BinaryFormatter();
    Stream stream = new MemoryStream();
    formatter.Serialize(stream, source);
    return stream;
}

public static T Deserialize<T>(Stream stream)
{
    IFormatter formatter = new BinaryFormatter();
    stream.Position = 0;
    return (T)formatter.Deserialize(stream);
}

public static T Clone<T>(object source)
{
    return Deserialize<T>(Serialize(source));
}
David Duffett
  • 3,145
  • 2
  • 26
  • 27
GeverGever
  • 519
  • 1
  • 3
  • 3
  • works perfectly. Comparing the original to the Cloned copy is a great test of serialization as checking IsSerializable only checks the attribute of the class and not base class or other properties – Catch22 Mar 02 '10 at 15:32
  • 1
    If you change the signature of the clone method to `public static T Clone(T source)` you get proper type inference for free. – fabsenet Dec 13 '16 at 10:50
17

I have this in some unit test here at job:

MyComplexObject dto = new MyComplexObject();
MemoryStream mem = new MemoryStream();
BinaryFormatter b = new BinaryFormatter();
try
{
    b.Serialize(mem, dto);
}
catch (Exception ex)
{
    Assert.Fail(ex.Message);
}

Might help you... maybe other method can be better but this one works well.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Patrick Desjardins
  • 136,852
  • 88
  • 292
  • 341
  • You need to be careful of NonSerialized (or transient in java) objects though, and those should be tested as part of your serialization and deserialization. – Egwor Oct 25 '08 at 17:32
  • @Egwor there are no NonSerialized objects, only NonSerialized fields. – Mishax Aug 29 '13 at 05:42
  • BinaryFormatter is obsolete in .Net6. Suggestions for .Net6? https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0011 – hardfork Sep 05 '22 at 11:07
16

In addition to the test above - which makes sure the serializer will accept your object, you need to do a round-trip test. Deserialize the results back to a new object and make sure the two instances are equivalent.

Scott Weinstein
  • 18,890
  • 14
  • 78
  • 115
3

Probably a bit late in the day, but if you are using the FluentAssertions library, then it has custom assertions for XML serialization, binary serialization, and data contract serialization.

theObject.Should().BeXmlSerializable();
theObject.Should().BeBinarySerializable();
theObject.Should().BeDataContractSerializable();

theObject.Should().BeBinarySerializable<MyClass>(
    options => options.Excluding(s => s.SomeNonSerializableProperty));
Boris Modylevsky
  • 3,029
  • 1
  • 26
  • 42
David Keaveny
  • 3,904
  • 2
  • 38
  • 52
2

serialize the object (to memory or disk), deserialize it, use reflection to compare the two, then run all of the unit tests for that object again (except serialization of course)

this assumes that your unit tests can accept an object as a target instead of making their own

Steven A. Lowe
  • 60,273
  • 18
  • 132
  • 202
  • I always comment on a down vote. You don't need to run tests again if the two have equivalent data. Beyond that when comparing data you have to understand that sometimes data changes when serializing/deserializing and need a way of expressing this (think about things that use the memento pattern like serializing a form the two may be equivalent after even though all the data is not the same) – Greg Young Feb 25 '11 at 16:26
  • 1
    @Greg: serilization is supposed to be identical, but as you point out may have fluctuations. The serialization routines and the comparison routines may also be incomplete or flawed, hence the recommendation to run all of the unit tests again. If they all pass on the reconstituted object, it's good to go. If they don't, this will indicate places where rehydration (and/or comparison) failed. – Steven A. Lowe Feb 25 '11 at 17:26
2

Here is a solution that recursively uses IsSerializable to check that the object and all its properties are Serializable.

    private static void AssertThatTypeAndPropertiesAreSerializable(Type type)
    {
        // base case
        if (type.IsValueType || type == typeof(string)) return;

        Assert.IsTrue(type.IsSerializable, type + " must be marked [Serializable]");

        foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (propertyInfo.PropertyType.IsGenericType)
            {
                foreach (var genericArgument in propertyInfo.PropertyType.GetGenericArguments())
                {
                    if (genericArgument == type) continue; // base case for circularly referenced properties
                    AssertThatTypeAndPropertiesAreSerializable(genericArgument);
                }
            }
            else if (propertyInfo.GetType() != type) // base case for circularly referenced properties
                AssertThatTypeAndPropertiesAreSerializable(propertyInfo.PropertyType);
        }
    }
Zaid Masud
  • 13,225
  • 9
  • 67
  • 88
  • The generic arguments don't need to be serializable for a type to be, for example `ObjectComparer` is serializable. – erikkallen Dec 16 '10 at 19:05
1

Unfortunately, you can't really test for this. Imagine this case:

[Serializable]
class Foo {
    public Bar MyBar { get; set; }
}

[Serializable]
class Bar {
    int x;
}

class DerivedBar : Bar {
}

public void TestSerializeFoo() {
    Serialize(new Foo()); // OK
    Serialize(new Foo() { MyBar = new Bar() }; // OK
    Serialize(new Foo() { MyBar = new DerivedBar() }; // Boom
}
erikkallen
  • 33,800
  • 13
  • 85
  • 120