how to test in Java that a class implements Serializable correctly (not just is an instance of Serializable)
Asked Answered
G

8

82

I am implementing a class to be Serializable (so it's a value object for use w/ RMI). But I need to test it. Is there a way to do this easily?

clarification: I'm implementing the class, so it's trivial to stick Serializable in the class definition. I need to manually serialize/deserialize it to see if it works.

I found this C# question, is there a similar answer for Java?

Grog answered 1/10, 2010 at 14:58 Comment(1)
Are you trying to test to see if the object gets serialized and deserialized properly?Expellant
T
138

The easy way is to check that the object is an instance of java.io.Serializable or java.io.Externalizable, but that doesn't really prove that the object really is serializable.

The only way to be sure is to try it for real. The simplest test is something like:

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);

and check it doesn't throw an exception.

Apache Commons Lang provides a rather more brief version:

SerializationUtils.serialize(myObject);

and again, check for the exception.

You can be more rigourous still, and check that it deserializes back into something equal to the original:

Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);

and so on.

Terchie answered 1/10, 2010 at 15:3 Comment(4)
Just in case anyone is curious or doesn't want to include the Apache Commons libraries, Spring also has SerializationUtils with the methods serialize and deserialize. See SerializationUtilsCarinthia
Your answer helped me, but deserialize() and clone() return Object, not Serializable.Winger
In the old commons-lang v2 that's true, but not since v3 - commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/…Terchie
Even this isn't foolproof. If an object has unserializable fields, but they are null, it will be serializable; inititialize them and it won't be.Geophagy
G
34

utility methods based on skaffman's answer:

private static <T extends Serializable> byte[] pickle(T obj) 
       throws IOException 
{
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);
    oos.close();
    return baos.toByteArray();
}

private static <T extends Serializable> T unpickle(byte[] b, Class<T> cl)
       throws IOException, ClassNotFoundException 
{
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object o = ois.readObject();
    return cl.cast(o);
}
Grog answered 1/10, 2010 at 15:48 Comment(3)
++thanks to you and skaffman! I was looking for something like this to add to a test harness to stop developers from writing code that is non-Serializable and killing our apps once they reach our clustered server. A life-saver! Thanks guys!Exudation
More about the "pickling" term here: docs.python.org/2/library/pickle.htmlRozina
Simple. Elegant. Thanks for this ... I'm going to presume that copy and paste is ok.Landmeier
O
3

This code should do it...

import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(isSerializable("Hello"));
        System.out.println(isSerializable(new Main()));
    }

    public static boolean isSerializable(final Object o)
    {
        final boolean retVal;

        if(implementsInterface(o))
        {
            retVal = attemptToSerialize(o);
        }
        else
        {
            retVal = false;
        }

        return (retVal);
    }

    private static boolean implementsInterface(final Object o)
    {
        final boolean retVal;

        retVal = ((o instanceof Serializable) || (o instanceof Externalizable));

        return (retVal);
    }

    private static boolean attemptToSerialize(final Object o)
    {
        final OutputStream sink;
        ObjectOutputStream stream;

        stream = null;

        try
        {
            sink   = new ByteArrayOutputStream();
            stream = new ObjectOutputStream(sink);
            stream.writeObject(o);
            // could also re-serilalize at this point too
        }
        catch(final IOException ex)
        {
            return (false);
        }
        finally
        {
            if(stream != null)
            {
                try
                {
                    stream.close();
                }
                catch(final IOException ex)
                {
                    // should not be able to happen
                }
            }
        }

        return (true);
    }
}
Onfroi answered 1/10, 2010 at 15:24 Comment(1)
instanceof Externalizable implies instanceof Serializable. You don't need to test both.Cobb
R
3

This only works for fully populated objects, if you require that any objects composed in your top level object are also serializable then they cannot be null for this test to be valid as serialization/deserialization skips the null objects

Rosenarosenbaum answered 7/6, 2013 at 3:56 Comment(0)
S
2

You can do following test:

  • Serialize object to file and make sure no exception is thrown.
  • Additionally, deserialize object back and compare with original object.

Here is example for serializing and deserializing object to file:

http://www.rgagnon.com/javadetails/java-0075.html

http://www.javapractices.com/topic/TopicAction.do?Id=57

Sherlock answered 1/10, 2010 at 15:4 Comment(0)
B
2

The short answer is, you can come up with some candidate objects and actually try to serialize them using the mechanism of your choice. The test here is that no errors are encountered during marshalling/unmarshalling, and that the resulting "rehydrated" object is equal to the original.

Alternatively, if you don't have any candidate objects, you could implement a reflection-based test that introspects the (non-static, non-transient) fields of your class to ensure that they too are Serializable. Speaking from experience, this gets surprisingly complex surprisingly quickly, but it can be done to a reasonable extent.

The downside to this latter approach is that if a field is e.g. List<String>, then you can either fail the class for not having a strictly serializable field, or simply assume that a serializable implementation of List will be used. Neither is perfect. (Mind you, the latter problem exists for examples too; if every example used in the test uses serializable Lists, there's nothing to prevent a non-serializable version being used by some other code in practice).

Biota answered 1/10, 2010 at 15:5 Comment(0)
G
0

I've attempted to write a unit test (in Groovy using Spock) which can check that a given interface for use with RMI is in fact fully serializable - all the parameters, exceptions, and possible implementations of types defined in the methods.

It seems to work for me so far, however, this is a bit fiddly to do and there may be cases this doesn't cover, so use at your own risk!

You will need to replace the example interfaces Notification etc. with your own. The example includes one unserializable field as an illustration.

package example

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import spock.lang.Specification

import java.lang.reflect.*
import java.rmi.Remote
import java.rmi.RemoteException

/** This checks that the a remoting API NotifierServer is safe
 *
 * It attempts to flush out any parameter classes which are
 * not Serializable. This isn't checked at compile time!
 *
 */
@CompileStatic
class RemotableInterfaceTest extends Specification {
    static class NotificationException extends RuntimeException {
        Object unserializable
    }

    static interface Notification {
        String getMessage()

        Date getDate()
    }

    static interface Notifier extends Remote {
        void accept(Notification notification) throws RemoteException, NotificationException
    }


    static interface NotifierServer extends Remote {
        void subscribe(Notification notifier) throws RemoteException
        void notify(Notification message) throws RemoteException
    }

    // From https://www.javaworld.com/article/2077477/learn-java/java-tip-113--identify-subclasses-at-runtime.html
    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    static Class[] getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader()
        assert classLoader != null
        String path = packageName.replace('.', '/')
        Enumeration resources = classLoader.getResources(path)
        List<File> dirs = new ArrayList()
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement()
            dirs.add(new File(resource.getFile()))
        }
        ArrayList classes = new ArrayList()
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName))
        }
        return classes.toArray(new Class[classes.size()])
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList()
        if (!directory.exists()) {
            return classes
        }
        File[] files = directory.listFiles()
        for (File file : files) {
            if (file.isDirectory()) {
                //assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()))
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)))
            }
        }
        return classes
    }

    /** Finds all known subclasses of a class */
    @CompileDynamic
    static List<Class> getSubclasses(Class type) {
        allClasses
            .findAll { Class it ->
                !Modifier.isAbstract(it.modifiers) &&
                it != type &&
                type.isAssignableFrom(it)
            }
    }

    /** Checks if a type is nominally serializable or remotable.
     *
     * Notes:
     * <ul>
     * <li> primitives are implicitly serializable
     * <li> interfaces are serializable or remotable by themselves, but we
     * assume that since #getSerializedTypes checks derived types of interfaces,
     * we can safely assume that all implementations will be checked
     *</ul>
     *
     * @param it
     * @return
     */
    static boolean isSerializableOrRemotable(Class<?> it) {
        return it.primitive || it.interface || Serializable.isAssignableFrom(it) || Remote.isAssignableFrom(it)
    }

    /** Recursively finds all (new) types associated with a given type 
     * which need to be serialized because they are fields, parameterized
     * types, implementations, etc. */
    static void getSerializedTypes(final Set<Class<?>> types, Type... it) {
        for(Type type in it) {
            println "type: $type.typeName"

            if (type instanceof GenericArrayType) {
                type = ((GenericArrayType)type).genericComponentType
            }

            if (type instanceof ParameterizedType) {
                ParameterizedType ptype = (ParameterizedType)type
                getSerializedTypes(types, ptype.actualTypeArguments)
                break
            }


            if (type instanceof Class) {
                Class ctype = (Class)type

                if (ctype == Object)
                    break

                if (types.contains(type))
                    break

                types << ctype
                for (Field field : ctype.declaredFields) {
                    println "${ctype.simpleName}.${field.name}: ${field.type.simpleName}"
                    if (Modifier.isVolatile(field.modifiers) ||
                        Modifier.isTransient(field.modifiers) ||
                        Modifier.isStatic(field.modifiers))
                        continue

                    Class<?> fieldType = field.type
                    if (fieldType.array)
                        fieldType = fieldType.componentType

                    if (types.contains(fieldType))
                        continue

                    types << fieldType
                    if (!fieldType.primitive)
                        getSerializedTypes(types, fieldType)
                }

                if (ctype.genericSuperclass) {
                    getSerializedTypes(types, ctype.genericSuperclass)
                }

                getSubclasses(ctype).each { Class c -> getSerializedTypes(types, c) }

                break
            }
        }
    }

    /** Recursively checks a type's methods for related classes which
     * need to be serializable if the type is remoted */
    static Set<Class<?>> getMethodTypes(Class<?> it) {
        Set<Class<?>> types = []
        for(Method method: it.methods) {
            println "method: ${it.simpleName}.$method.name"
            getSerializedTypes(types, method.genericParameterTypes)
            getSerializedTypes(types, method.genericReturnType)
            getSerializedTypes(types, method.genericExceptionTypes)
        }
        return types
    }

    /** All the known defined classes */
    static List<Class> allClasses = Package.packages.collectMany { Package p -> getClasses(p.name) as Collection<Class> }


    @CompileDynamic
    def "NotifierServer interface should only expose serializable or remotable types"() {
        given:
        Set<Class> types = getMethodTypes(NotifierServer)

        Set<Class> nonSerializableTypes = types.findAll { !isSerializableOrRemotable(it) }

        expect:
        nonSerializableTypes.empty
    }

}
Geophagy answered 7/8, 2018 at 17:21 Comment(0)
J
0

Create POJO class:

package com.example.simpleproject.dto;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
@Getter
@Setter
@EqualsAndHashCode
@ToString
public class ExtraInfo implements Serializable {

    private String phoneNumber;
    private String other;
}

Create test class and write next unit test method:

package com.example.simpleproject.dto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.SerializationUtils;
import java.io.Serializable;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ExtraInfoTest {

    private ExtraInfo objectUnderTest;

    @BeforeEach
    void setUp() {
        objectUnderTest = new ExtraInfo();
    }

    @Test
    void testSerializable() {
        Serializable serialized = SerializationUtils.serialize(objectUnderTest);
        Object deserialized = SerializationUtils.deserialize((byte[]) serialized);
        assertEquals(objectUnderTest, deserialized);
    }
}
Janes answered 20/10, 2023 at 14:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.