Skip to content

Instantly share code, notes, and snippets.

@mjpitz
Last active March 7, 2023 17:32
Show Gist options
  • Save mjpitz/79ac7bfe7d5f6f064b38 to your computer and use it in GitHub Desktop.
Save mjpitz/79ac7bfe7d5f6f064b38 to your computer and use it in GitHub Desktop.
A class that uses reflection to automate the testing of getters and setters.
import com.google.common.base.Defaults;
import com.google.common.collect.Sets;
import javax.annotation.Nonnull;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import static org.junit.Assert.assertEquals;
/**
* Automates JUnit testing of simple getter / setter methods.
*
* <p>
* This class was modeled after the {@link EqualsVerifier} approach where in a
* few lines of code, you can test the entirety of a simple Java object. For
* example:
* </p>
*
* <pre>
* GetterSetterVerifier.forClass(MyClass.class)
* .verify();
* </pre>
*
* <p>
* You can also specify which properties you do no want to test in the event
* that the associated getters and setters are non-trivial. For example:
* </p>
*
* <pre>
* GetterSetterVerifier.forClass(MyClass.class)
* .exclude("someComplexProperty")
* .exclude("anotherComplexProperty")
* .verify();
* </pre>
*
* <p>
* On the other hand, if you'd rather be more verbose about what properties are
* tested, you can specify them using the include syntax. When using the
* include approach, only the properties that you specified will be tested.
* For example:
* </p>
*
* <pre>
* GetterSetterVerifier.forClass(MyClass.class)
* .include("someSimpleProperty")
* .include("anotherSimpleProperty")
* .verify();
* </pre>
*
* References:
* {@link http://www.jqno.nl/equalsverifier/}
* {@link https://www.altamiracorp.com/blog/employee-posts/do-you-unit-test-getters}
* {@link http://vcs.patapouf.org/svn/glibersat/STANdsl/STAN/src-tester/com/midwinter/junit/GetterSetterTester.java}
*/
public class GetterSetterVerifier<T> {
private Class<T> type;
private Set<String> excludes;
private Set<String> includes;
/**
* Creates a getter / setter verifier to test properties for a particular class.
*
* @param type The class that we are testing
*/
private GetterSetterVerifier(@Nonnull final Class<T> type) {
this.type = type;
}
/**
* Method used to identify the properties that we are going to test. If
* no includes are specified, then all the properties are considered for
* testing.
*
* @param include The name of the property that we are going to test.
* @return This object, for method chaining.
*/
public GetterSetterVerifier<T> include(@Nonnull final String include) {
if (includes == null) {
includes = Sets.newHashSet();
}
includes.add(include);
return this;
}
/**
* Method used to identify the properties that will be ignored during
* testing. If no excludes are specified, then no properties will be
* excluded.
*
* @param exclude The name of the property that we are going to ignore.
* @return This object, for method chaining.
*/
public GetterSetterVerifier<T> exclude(@Nonnull final String exclude) {
if (excludes == null) {
excludes = Sets.newHashSet();
}
excludes.add(exclude);
return this;
}
/**
* Verify the class's getters and setters
*/
public void verify() {
try {
final BeanInfo beanInfo = Introspector.getBeanInfo(type);
final PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
for (final PropertyDescriptor property : properties) {
if (shouldTestProperty(property)) {
testProperty(property);
}
}
} catch (final Exception e) {
throw new AssertionError(e.getMessage());
}
}
/**
* Determine if we need to test the property based on a few conditions.
* 1. The property has both a getter and a setter.
* 2. The property was not excluded.
* 3. The property was considered for testing.
*
* @param property The property that we are determining if we going to test.
* @return True if we should test the property. False if we shouldn't.
*/
private boolean shouldTestProperty(@Nonnull final PropertyDescriptor property) {
if (property.getWriteMethod() == null || property.getReadMethod() == null) {
return false;
} else if (excludes != null && excludes.contains(property.getDisplayName())) {
return false;
}
return includes == null || includes.contains(property.getDisplayName());
}
/**
* Test an individual property by getting the read method and write method
* and passing the default value for the type to the setter and asserting
* that the same value was returned.
*
* @param property The property that we are testing.
*
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
*/
private void testProperty(@Nonnull final PropertyDescriptor property) throws IllegalAccessException,
InstantiationException,
InvocationTargetException {
final Object target = type.newInstance();
final Object setValue = Defaults.defaultValue(property.getPropertyType());
final Method getter = property.getReadMethod();
final Method setter = property.getWriteMethod();
setter.invoke(target, setValue);
final Object getValue = getter.invoke(target);
assertEquals(
property.getDisplayName() + " getter / setter do not produce the same result.",
setValue, getValue
);
}
/**
* Factory method for easily creating a test for the getters and setters.
*
* @param type The class that we are testing the getters and setters for.
* @return An object that can be used for testing the getters and setters
* of a class.
*/
public static <T> GetterSetterVerifier<T> forClass(@Nonnull final Class<T> type) {
return new GetterSetterVerifier<T>(type);
}
}
@adil-hussain-84
Copy link

adil-hussain-84 commented Mar 27, 2017

Good idea but would be better if the value being set for the property under test was not the default value for that type of property. Given a class as follows...

public class Person {
    private String name;
    public String getName() { return null; }
    public void setName(String name) { this.name = name; }
}

... your GetterSetterVerifier class deems the Person class to have a valid getter and setter.

@rohit0903
Copy link

I am new to JUnit, may I know how to run this ? I don't see any method annotated with @test. Kindly revert !

@sleepypikachu
Copy link

sleepypikachu commented Apr 27, 2018

@rohit0903
Always read the docs ;-)
GetterSetterVerifier.forClass(MyClass.class).verify();

So

public class MyClassTest {
    // will fail if your getter/setters don't work
    @Test
    public void getterSetterTest() {
       GetterSetterVerifier.forClass(MyClass.class).verify();
    }
    // other tests as needed
}

@ElMassas
Copy link

I'm sorry, I'm also new to JUnit, should I creat this class in a test directory?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment