3

What I need to do is having a unit test that checks whether a given class MyClass is serializable. But I can't only check that the class implements Serializable: all the attributes of the class (and the attributes of the attributes etc.) have to implement Serializable.

Also, a solution like creating an instance of MyClass and try to serialize and deserialize it is not satisfactory: if someone adds an attribute and does not update the unit test, the unit test won't test the new attribute.

One solution would probably be to use reflection recursively to test whether the classes of all the attributes implement Serializable, but that seems really heavy.

Isn't there a better solution?

Thanks.

mael
  • 2,214
  • 1
  • 19
  • 20
  • 1
    try to serialize the object and if it throws an exception ... – Scary Wombat May 28 '14 at 07:27
  • 2
    This is, basically, impossible. If your class has a field of type Object, or List, or Set, or Collection, or any other non-serializable interface, you won't be able to determine if an instance of the class is serializable. Serializability is a runtime feature more than a compile-time feature. What matters is the concrete type of the fields at runtime, and not their declared type. Use integration tests. – JB Nizet May 28 '14 at 07:30
  • go through following link http://stackoverflow.com/questions/3840356/how-to-test-in-java-that-a-class-implements-serializable-correctly-not-just-is – rachana May 28 '14 at 07:34
  • @Scary Wombat & rachana: please read my second paragraph: I do not think that's a good solution. The unit test will work but whenever someone adds an attribute to MyClass, the test won't automatically test the new attribute. – mael May 28 '14 at 07:41

5 Answers5

3

This is an old question, but I thought I would add my 2c.

Even if you recurse through the whole object, you cannot guarantee that your object implements serializable. If you have a member variable that is abstract or is an interface, you cannot say if the object you eventually store will or will not be serialized. If you know what you are doing, it would serialized.

This answer provides a solution by populating your objects with random data, but this is probably overkill.

I have created a class that does recurse through the structure ignoring interfaces, java.lang.Object and abstract classes.

package au.com.tt.util.test;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class SerializableChecker
{
    public static class SerializationFailure
    {
        private final String mContainingClass;
        private final String mMemberName;

        public SerializationFailure(String inNonSerializableClass, String inMemberName)
        {
            mContainingClass = inNonSerializableClass;
            mMemberName = inMemberName;
        }

        public String getContainingClass()
        {
            return mContainingClass;
        }

        public String getMemberName()
        {
            return mMemberName;
        }

        public String getBadMemberString()
        {
            if (mMemberName == null)
                return mContainingClass;
            return mContainingClass + "." + mMemberName;
        }

        @Override
        public String toString()
        {
            return "SerializationFailure [mNonSerializableClass=" + mContainingClass + ", mMemberName=" + mMemberName + "]";
        }
    }

    private static class SerializationCheckerData
    {
        private Set<Class<?>> mSerializableClasses;

        SerializationCheckerData()
        {
            mSerializableClasses = new HashSet<Class<?>>();
        }

        boolean isAlreadyChecked(Class<?> inClass)
        {
            return mSerializableClasses.contains(inClass);
        }

        void addSerializableClass(Class<?> inClass)
        {
            mSerializableClasses.add(inClass);
        }
    }

    private SerializableChecker()
    { }

    public static SerializationFailure isFullySerializable(Class<?> inClass)
    {
        if (!isSerializable(inClass))
            return new SerializationFailure(inClass.getName(), null);

        return isFullySerializable(inClass, new SerializationCheckerData());
    }

    private static SerializationFailure isFullySerializable(Class<?> inClass, SerializationCheckerData inSerializationCheckerData)
    {
        for (Field field : declaredFields(inClass))
        {
            Class<?> fieldDeclaringClass = field.getType();

            if (field.getType() == Object.class)
                continue;

            if (Modifier.isStatic(field.getModifiers()))
                continue;

            if (field.isSynthetic())
                continue;

            if (fieldDeclaringClass.isInterface() || fieldDeclaringClass.isPrimitive())
                continue;

            if (Modifier.isAbstract(field.getType().getModifiers()))
                continue;

            if (inSerializationCheckerData.isAlreadyChecked(fieldDeclaringClass))
                continue;

            if (isSerializable(fieldDeclaringClass))
            {
                inSerializationCheckerData.addSerializableClass(inClass);

                SerializationFailure failure = isFullySerializable(field.getType(), inSerializationCheckerData);
                if (failure != null)
                    return failure;
                else
                    continue;
            }

            if (Modifier.isTransient(field.getModifiers()))
                continue;

            return new SerializationFailure(field.getDeclaringClass().getName(), field.getName());
        }
        return null;
    }

    private static boolean isSerializable(Class<?> inClass)
    {
        Set<Class<?>> interfaces = getInterfaces(inClass);
        if (interfaces == null)
            return false;
        boolean isSerializable = interfaces.contains(Serializable.class);
        if (isSerializable)
            return true;

        for (Class<?> classInterface : interfaces)
        {
            if (isSerializable(classInterface))
                return true;
        }

        if (inClass.getSuperclass() != null && isSerializable(inClass.getSuperclass()))
            return true;

        return false;
    }

    private static Set<Class<?>> getInterfaces(Class<?> inFieldDeclaringClass)
    {
        return new HashSet<Class<?>>(Arrays.asList(inFieldDeclaringClass.getInterfaces()));
    }

    private static List<Field> declaredFields(Class<?> inClass)
    {
        List<Field> fields = new ArrayList<Field>(Arrays.asList(inClass.getDeclaredFields()));

        Class<?> parentClasses = inClass.getSuperclass();

        if (parentClasses == null)
            return fields;
        fields.addAll(declaredFields(parentClasses));

        return fields;
        }
    }
Community
  • 1
  • 1
user1545858
  • 725
  • 4
  • 21
1

As a general check, you can use SerializationUtils from Apache Commons, as follows:

byte [] data = SerializationUtils.serialize(obj);
Object objNew = SerializationUtils.deserialize(data);

but it does not guarantee that other instances of that class will serialize properly; for complex objects there is a risk that particular members will not be serializable depending on their members.

Erica Kane
  • 3,137
  • 26
  • 36
1

Using reflection recursively is not perfect but could partially makes the job.

To do that, you can re-use an utility class like this.

Usage will look like this :

public class SerializationTests {

    @Test
    public void ensure_MyClass_is_serializable() {
        assertIsSerializable(MyClass.class);
    }

    @Test
    public void ensure_MyComplexClass_is_serializable() {
        // We excludes "org.MyComplexClass.attributeName" 
        // because we can not be sure it is serializable in a
        // reliable way.
        assertIsSerializable(MyComplexClass.class, "org.MyComplexClass.attributeName");
    }

    private static void assertIsSerializable(Class<?> clazz, String... excludes) {
        Map<Object, String> results = SerializationUtil.isSerializable(clazz, excludes);

        if (!results.isEmpty()) {
            StringBuilder issues = new StringBuilder();
            for (String issue : results.values()) {
                issues.append("\n");
                issues.append(issue);
            }
            fail(issues.toString());
        }
    }
}
sbernard
  • 444
  • 3
  • 16
  • Note, that utility class on that link is a bit behind master. Also note, that this utility does not check parent classes. – Peter Jul 03 '20 at 10:14
  • @Peter, I updated the link. You mean "fields of parent classes are not checked" ? I can add it if needed. (just open a ticket on leshan github issue) – sbernard Jul 06 '20 at 13:25
  • Yes I meant parent fields. But I also meant parent class, since it can make child class serializable (when only parent implements `Serializable`). Actually I already modified it, to fit my purposes. I can make a PR for you, if you want. It may be a bit hard to follow what changed tough, since I also did some refactoring and was lazy enough to not make commits in between. – Peter Jul 07 '20 at 12:48
  • You can create a PR if you find time. – sbernard Jul 30 '20 at 15:22
0

I think serialize and deserialize instance is OK. If class is changed unit test will still be valid, you need to compare original and deserialized objects

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • I don't think that's OK. If a developer adds an attribute that is not serializable, the test won't fail, but the serialization will fail in production if the attribute is not null. – mael May 28 '14 at 07:36
  • @mael: that's not a valid argument: if a developer modifies a class, he/she should modify the unit test accordingly. You can't expect a unit test to be valid forever, without modification. – JB Nizet May 28 '14 at 07:40
  • @JB Nizet: that's the theory. But in practice the developer will take a look at the unit test that is directly related to the class being modified + the unit tests that possibly fail. But if he modifies the class MyThirdClass and MyClass contains a "MySecondClass" attribute which contains a "MyThirdClass" attribute, I'm pretty sure the developer will forget to change the unit test. But I think you're right in your other comment: that should probably be an integration test. Thanks. – mael May 28 '14 at 07:48
  • @mael if it's not null, I think the test would fail, and that's how it should be. – Axel May 28 '14 at 07:51
0

An easy way to test if an object is serializable in Kotlin :

 /**
 * Check if an object is Serializable
 */
fun assertIsSerializable(obj: Any) {
    val serialized = SerializationUtils.serialize(obj as Serializable)
    val unSerialized = SerializationUtils.deserialize<Any>(serialized)
}
Ben-J
  • 1,084
  • 8
  • 24