Package com.google.common.testing
Class ClassSanityTester
- java.lang.Object
-
- com.google.common.testing.ClassSanityTester
-
@Beta @GwtIncompatible public final class ClassSanityTester extends java.lang.Object
Tester that runs automated sanity tests for any given class. A typical use case is to test static factory classes like:interface Book {...} public class Books { public static Book hardcover(String title) {...} public static Book paperback(String title) {...} }
And all the created
Book
instances can be tested with:new ClassSanityTester() .forAllPublicStaticMethods(Books.class) .thatReturn(Book.class) .testEquals(); // or testNulls(), testSerializable() etc.
- Since:
- 14.0
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description (package private) static class
ClassSanityTester.FactoryMethodReturnsNullException
Thrown if the test tries to invoke a static factory method to test instance methods but the factory returned null.class
ClassSanityTester.FactoryMethodReturnValueTester
Runs sanity tests against return values of static factory methods declared by a class.(package private) static class
ClassSanityTester.ParameterHasNoDistinctValueException
Thrown if the test fails to generate two distinct non-null values of a constructor or factory parameter in order to testObject.equals(java.lang.Object)
andObject.hashCode()
of the declaring class.(package private) static class
ClassSanityTester.ParameterNotInstantiableException
Thrown if the test tries to invoke a constructor or static factory method but failed because the dummy value of a constructor or method parameter is unknown.private static class
ClassSanityTester.SerializableDummyProxy
-
Field Summary
Fields Modifier and Type Field Description private static Ordering<Invokable<?,?>>
BY_METHOD_NAME
private static Ordering<Invokable<?,?>>
BY_NUMBER_OF_PARAMETERS
private static Ordering<Invokable<?,?>>
BY_PARAMETERS
private MutableClassToInstanceMap<java.lang.Object>
defaultValues
private ListMultimap<java.lang.Class<?>,java.lang.Object>
distinctValues
private NullPointerTester
nullPointerTester
-
Constructor Summary
Constructors Constructor Description ClassSanityTester()
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description private static <T> T
createInstance(Invokable<?,? extends T> factory, java.util.List<?> args)
(package private) void
doTestEquals(java.lang.Class<?> cls)
(package private) void
doTestNulls(java.lang.Class<?> cls, NullPointerTester.Visibility visibility)
ClassSanityTester.FactoryMethodReturnValueTester
forAllPublicStaticMethods(java.lang.Class<?> cls)
Returns an object responsible for performing sanity tests against the return values of all public static methods declared bycls
, excluding superclasses.private static java.lang.Object
generateDummyArg(Parameter param, FreshValueGenerator generator)
private java.util.List<java.lang.Object>
generateEqualFactoryArguments(Invokable<?,?> factory, java.util.List<Parameter> params, java.util.List<java.lang.Object> args)
Returns dummy factory arguments that are equal toargs
but may be different instances, to be used to construct a second instance of the same equality group.private java.util.List<java.lang.Object>
getDummyArguments(Invokable<?,?> invokable)
private <T> T
getDummyValue(TypeToken<T> type)
private static <T> ImmutableList<Invokable<?,? extends T>>
getFactories(TypeToken<T> type)
Factories with the least number of parameters are listed first.private static boolean
hashCodeInsensitiveToArgReference(Invokable<?,?> factory, java.util.List<java.lang.Object> args, int i, java.lang.Object alternateArg)
private boolean
hasInstanceMethodToTestNulls(java.lang.Class<?> c, NullPointerTester.Visibility visibility)
private <T> T
instantiate(Invokable<?,? extends T> factory)
Instantiates usingfactory
.(package private) <T> T
instantiate(java.lang.Class<T> cls)
Instantiatescls
by invoking one of its non-private constructors or non-private static factory methods with the parameters automatically provided using dummy values.private static <T> T
invoke(Invokable<?,? extends T> factory, java.util.List<?> args)
private FreshValueGenerator
newFreshValueGenerator()
<T> ClassSanityTester
setDefault(java.lang.Class<T> type, T value)
Sets the default value fortype
.<T> ClassSanityTester
setDistinctValues(java.lang.Class<T> type, T value1, T value2)
Sets distinct values fortype
, so that when a classFoo
is tested forObject.equals(java.lang.Object)
andObject.hashCode()
, and its construction requires a parameter oftype
, the distinct values oftype
can be passed as parameters to createFoo
instances that are unequal.void
testEquals(java.lang.Class<?> cls)
Tests theObject.equals(java.lang.Object)
andObject.hashCode()
ofcls
.private void
testEqualsUsing(Invokable<?,?> factory)
void
testNulls(java.lang.Class<?> cls)
Tests thatcls
properly checks null on all constructor and method parameters that aren't annotated nullable (according to the rules ofNullPointerTester
).private static <X extends java.lang.Throwable>
voidthrowFirst(java.util.List<X> exceptions)
-
-
-
Field Detail
-
defaultValues
private final MutableClassToInstanceMap<java.lang.Object> defaultValues
-
distinctValues
private final ListMultimap<java.lang.Class<?>,java.lang.Object> distinctValues
-
nullPointerTester
private final NullPointerTester nullPointerTester
-
-
Method Detail
-
setDefault
public <T> ClassSanityTester setDefault(java.lang.Class<T> type, T value)
Sets the default value fortype
. The default value isn't used in testingObject.equals(java.lang.Object)
because more than one sample instances are needed for testing inequality. To set distinct values for equality testing, usesetDistinctValues(java.lang.Class<T>, T, T)
instead.
-
setDistinctValues
public <T> ClassSanityTester setDistinctValues(java.lang.Class<T> type, T value1, T value2)
Sets distinct values fortype
, so that when a classFoo
is tested forObject.equals(java.lang.Object)
andObject.hashCode()
, and its construction requires a parameter oftype
, the distinct values oftype
can be passed as parameters to createFoo
instances that are unequal.Calling
setDistinctValues(type, v1, v2)
also sets the default value fortype
that's used fortestNulls(java.lang.Class<?>)
.Only necessary for types where
ClassSanityTester
doesn't already know how to create distinct values.- Returns:
- this tester instance
- Since:
- 17.0
-
testNulls
public void testNulls(java.lang.Class<?> cls)
Tests thatcls
properly checks null on all constructor and method parameters that aren't annotated nullable (according to the rules ofNullPointerTester
). In details:- All non-private static methods are checked such that passing null for any parameter
that's not annotated nullable should throw
NullPointerException
. - If there is any non-private constructor or non-private static factory method declared by
cls
, all non-private instance methods will be checked too using the instance created by invoking the constructor or static factory method. - If there is any non-private constructor or non-private static factory method declared by
cls
:- Test will fail if default value for a parameter cannot be determined.
- Test will fail if the factory method returns null so testing instance methods is impossible.
- Test will fail if the constructor or factory method throws exception.
- If there is no non-private constructor or non-private static factory method declared by
cls
, instance methods are skipped for nulls test. - Nulls test is not performed on method return values unless the method is a non-private
static factory method whose return type is
cls
orcls
's subtype.
- All non-private static methods are checked such that passing null for any parameter
that's not annotated nullable should throw
-
doTestNulls
void doTestNulls(java.lang.Class<?> cls, NullPointerTester.Visibility visibility) throws ClassSanityTester.ParameterNotInstantiableException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassSanityTester.FactoryMethodReturnsNullException
- Throws:
ClassSanityTester.ParameterNotInstantiableException
java.lang.IllegalAccessException
java.lang.reflect.InvocationTargetException
ClassSanityTester.FactoryMethodReturnsNullException
-
hasInstanceMethodToTestNulls
private boolean hasInstanceMethodToTestNulls(java.lang.Class<?> c, NullPointerTester.Visibility visibility)
-
testEquals
public void testEquals(java.lang.Class<?> cls)
Tests theObject.equals(java.lang.Object)
andObject.hashCode()
ofcls
. In details:- The non-private constructor or non-private static factory method with the most parameters is used to construct the sample instances. In case of tie, the candidate constructors or factories are tried one after another until one can be used to construct sample instances.
- For the constructor or static factory method used to construct instances, it's checked that when equal parameters are passed, the result instance should also be equal; and vice versa.
- If a non-private constructor or non-private static factory method exists:
- Test will fail if default value for a parameter cannot be determined.
- Test will fail if the factory method returns null so testing instance methods is impossible.
- Test will fail if the constructor or factory method throws exception.
- If there is no non-private constructor or non-private static factory method declared by
cls
, no test is performed. - Equality test is not performed on method return values unless the method is a non-private
static factory method whose return type is
cls
orcls
's subtype. - Inequality check is not performed against state mutation methods such as
List.add(E)
, or functional update methods such asJoiner.skipNulls()
.
Note that constructors taking a builder object cannot be tested effectively because semantics of builder can be arbitrarily complex. Still, a factory class can be created in the test to facilitate equality testing. For example:
public class FooTest { private static class FooFactoryForTest { public static Foo create(String a, String b, int c, boolean d) { return Foo.builder() .setA(a) .setB(b) .setC(c) .setD(d) .build(); } } public void testEquals() { new ClassSanityTester() .forAllPublicStaticMethods(FooFactoryForTest.class) .thatReturn(Foo.class) .testEquals(); } }
It will test that Foo objects created by the
create(a, b, c, d)
factory method with equal parameters are equal and vice versa, thus indirectly tests the builder equality.
-
doTestEquals
void doTestEquals(java.lang.Class<?> cls) throws ClassSanityTester.ParameterNotInstantiableException, ClassSanityTester.ParameterHasNoDistinctValueException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassSanityTester.FactoryMethodReturnsNullException
- Throws:
ClassSanityTester.ParameterNotInstantiableException
ClassSanityTester.ParameterHasNoDistinctValueException
java.lang.IllegalAccessException
java.lang.reflect.InvocationTargetException
ClassSanityTester.FactoryMethodReturnsNullException
-
instantiate
<T> T instantiate(java.lang.Class<T> cls) throws ClassSanityTester.ParameterNotInstantiableException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassSanityTester.FactoryMethodReturnsNullException
Instantiatescls
by invoking one of its non-private constructors or non-private static factory methods with the parameters automatically provided using dummy values.- Returns:
- The instantiated instance, or
null
if the class has no non-private constructor or factory method to be constructed. - Throws:
ClassSanityTester.ParameterNotInstantiableException
java.lang.IllegalAccessException
java.lang.reflect.InvocationTargetException
ClassSanityTester.FactoryMethodReturnsNullException
-
instantiate
private <T> T instantiate(Invokable<?,? extends T> factory) throws ClassSanityTester.ParameterNotInstantiableException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException
Instantiates usingfactory
. Iffactory
is annotated nullable and returns null, null will be returned.- Throws:
ClassSanityTester.ParameterNotInstantiableException
- if the static methods cannot be invoked because the default value of a parameter cannot be determined.java.lang.IllegalAccessException
- if the class isn't public or is nested inside a non-public class, preventing its methods from being accessible.java.lang.reflect.InvocationTargetException
- if a static method threw exception.
-
forAllPublicStaticMethods
public ClassSanityTester.FactoryMethodReturnValueTester forAllPublicStaticMethods(java.lang.Class<?> cls)
Returns an object responsible for performing sanity tests against the return values of all public static methods declared bycls
, excluding superclasses.
-
testEqualsUsing
private void testEqualsUsing(Invokable<?,?> factory) throws ClassSanityTester.ParameterNotInstantiableException, ClassSanityTester.ParameterHasNoDistinctValueException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassSanityTester.FactoryMethodReturnsNullException
- Throws:
ClassSanityTester.ParameterNotInstantiableException
ClassSanityTester.ParameterHasNoDistinctValueException
java.lang.IllegalAccessException
java.lang.reflect.InvocationTargetException
ClassSanityTester.FactoryMethodReturnsNullException
-
generateEqualFactoryArguments
private java.util.List<java.lang.Object> generateEqualFactoryArguments(Invokable<?,?> factory, java.util.List<Parameter> params, java.util.List<java.lang.Object> args) throws ClassSanityTester.ParameterNotInstantiableException, ClassSanityTester.FactoryMethodReturnsNullException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException
Returns dummy factory arguments that are equal toargs
but may be different instances, to be used to construct a second instance of the same equality group.- Throws:
ClassSanityTester.ParameterNotInstantiableException
ClassSanityTester.FactoryMethodReturnsNullException
java.lang.reflect.InvocationTargetException
java.lang.IllegalAccessException
-
hashCodeInsensitiveToArgReference
private static boolean hashCodeInsensitiveToArgReference(Invokable<?,?> factory, java.util.List<java.lang.Object> args, int i, java.lang.Object alternateArg) throws ClassSanityTester.FactoryMethodReturnsNullException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException
- Throws:
ClassSanityTester.FactoryMethodReturnsNullException
java.lang.reflect.InvocationTargetException
java.lang.IllegalAccessException
-
newFreshValueGenerator
private FreshValueGenerator newFreshValueGenerator()
-
generateDummyArg
private static java.lang.Object generateDummyArg(Parameter param, FreshValueGenerator generator) throws ClassSanityTester.ParameterNotInstantiableException
-
throwFirst
private static <X extends java.lang.Throwable> void throwFirst(java.util.List<X> exceptions) throws X extends java.lang.Throwable
- Throws:
X extends java.lang.Throwable
-
getFactories
private static <T> ImmutableList<Invokable<?,? extends T>> getFactories(TypeToken<T> type)
Factories with the least number of parameters are listed first.
-
getDummyArguments
private java.util.List<java.lang.Object> getDummyArguments(Invokable<?,?> invokable) throws ClassSanityTester.ParameterNotInstantiableException
-
getDummyValue
private <T> T getDummyValue(TypeToken<T> type)
-
createInstance
private static <T> T createInstance(Invokable<?,? extends T> factory, java.util.List<?> args) throws ClassSanityTester.FactoryMethodReturnsNullException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException
- Throws:
ClassSanityTester.FactoryMethodReturnsNullException
java.lang.reflect.InvocationTargetException
java.lang.IllegalAccessException
-
invoke
private static <T> T invoke(Invokable<?,? extends T> factory, java.util.List<?> args) throws java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException
- Throws:
java.lang.reflect.InvocationTargetException
java.lang.IllegalAccessException
-
-