Writing JUnit tests for trivial beans is annoying to say the least. Still debated is whether you should really test setters and getters. The testing Nazis and code coverage nuts insist upon it. Whether or not you agree with that, you should at least give some consideration to the question.
I'm assuming you care, I do—sort of. So, here's some code examining the problem. I don't know whether I can ever solve the absolute automation thereof or not. So far not.
Don't assume that bean properties are always so simple. If a property can't be null or, if a String can't be zero-length, then you have a dilemma: either handle check in consuming code or handle it in the bean. Which should you do? I don't have an opinion yet, but I lean toward checking it in the bean because it belongs to the bean which should be totally black-boxed in my opinion. You should always be able to rely on what the bean produces.
But, if the bean hasn't had valid values injected before it's called, what do you do? Throw an exception? Return some default value? We must answer questions about:
However, aren't these questions better asked in the context of the bean and not so much only the bean's test code?
Some coverage analyzers might be fooled by the use of the testing utility I'm about to demonstrate and help you therefore exclude getters and setters from those code paths that escape testing. So, I'm not certain even that is a benefit if it causes your testing to overlook getters and setters doing significant things or, worse, it induces you by means of an artificially high percentage of coverage to think that your product is somehow quality tested.
(Utimately, this is a useless digression, but hear me out.)
When contemplating how tests could be set up painlessly for an object's accessor methods, reflection comes to mind. It's easy and, as it's for testing, it's not going to matter if it's a little slow.
By stepping through this code in the debugger, you can get a feeling for how your JUnit test could handle testing a bean's properties (or fields or setters and getters for those fields).
This class is a) its own bean and b) a consumer of Apache Commons code written partly to analyze beans. In order to run this code, you must include commons-beanutils-x.x.x.jar and commons-logging-x.x.x.jar in your project; these are commonly part of web application code anyway. (The actual name depends on what version is current when you get this: I used commons-beanutils-1.7.0.jar and commons-logging-1.1.1.jar.)
package com.etretatlogiciels.supplement.testing; import java.io.Serializable; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; public class QuickieBeanTest implements Serializable { static final long serialVersionUID = 1; private int a, b; private String x, y; public int getA() { return this.a; } public void setA( int a ) { this.a = a; } public int getB() { return this.b; } public void setB( int b ) { this.b = b; } public String getX() { return this.x; } public void setX( String x ) { this.x = x; } public String getY() { return this.y; } public void setY( String y ) { this.y = y; } public static void main( String[] args ) throws Exception { QuickieBeanTest bean = new QuickieBeanTest(); bean.setA( 99 ); bean.setX( "crapola" ); Map< String, Object > map = cast( PropertyUtils.describe( bean ) ); Set< String > fields = map.keySet(); for( Object o : fields ) { String key = ( String ) o; Object value = map.get( o ); System.out.print( key + " = " + value ); if( !key.equals( "class" ) ) // don't blow chunks: the "class" isn't a field, eh! { PropertyUtils.setSimpleProperty( bean, key, value ); Object vvalue = PropertyUtils.getSimpleProperty( bean, key ); System.out.print( " (" + vvalue + ")" ); } System.out.println(); } } @SuppressWarnings( "unchecked" ) private static final < X > X cast( Object o ) { return ( X ) o; } }
The output from this experiment is:
b = 0 (0) a = 99 (99) class = class com.etretatlogiciels.supplement.testing.QuickieBeanTest y = null (null) x = crapola (crapola)
From the above test, we can imagine a utility testing class, TestBean. First, however, let's posit the bean we're going to test. We create the class, add in the fields (properties), and ask Eclipse to generate the getters and setters:
package com.etretatlogiciels.supplement.testing; public class BeanToTest { private int x = 99; private String kitty = "cat"; private Object o = null; public int getX() { return this.x; } public void setX( int x ) { this.x = x; } public String getKitty() { return this.kitty; } public void setKitty( String kitty ){ this.kitty = kitty; } public Object getO() { return this.o; } public void setO( Object o ) { this.o = o; } }
From our earlier experiment, here's the finished unit testing utility. The main() method, commented-out, demonstrates how to consume this utility from your JUnit test code.
package com.etretatlogiciels.supplement.testing; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.apache.log4j.Logger; /** * Try this class for testing the getters and setters in your beans. You must * handle the three exceptions thrown by testProperties() since that's * one way of indicting the tested bean as inadequate or broken. We could catch * these exceptions which properly belong to the Apache Commons class we're using * here, but those very exceptions tells us of something wrong in the bean we're * testing. * * IllegalAccessException - no access to the bean being tested * InvocationTargetException - problem invoking a setter/getter * NoSuchMethodException - missing setter/getter (unlikely to happen) * * Note: This class requires log4j. It also requires log4j * configuration in the consuming application (such as done in log4j.properties) * or harmless log4j warnings will issue. * * @author Russell Bateman */ public class TestBean { private static Logger log = Logger.getLogger( TestBean.class ); private String name = null; private Object bean = null; public TestBean( String name, Object bean ) { this.name = name; this.bean = bean; } public void testPropertiesToConsole( boolean toConsole ) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Map< String, Object > map = cast( PropertyUtils.describe( this.bean ) ); Set< String > fields = map.keySet(); for( Object o : fields ) { String key = ( String ) o; Object value = map.get( o ); String message = key + " = " + value; if( !key.equals( "class" ) ) // don't blow chunks: "class" isn't a field, eh! { PropertyUtils.setSimpleProperty( this.bean, key, value ); Object vvalue = PropertyUtils.getSimpleProperty( this.bean, key ); message += " (" + vvalue + ")"; } log.debug( message ); if( toConsole ) System.out.println( message ); } } /** * Test all the setters and getters of the bean in this instance. * * Note: use testPropertiesToConsole() to get output on your console * while running. Both versions write to log.debug(), however. * * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchMethodException */ public void testProperties() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { testPropertiesToConsole( false ); } /** * Test all the setters and getters of the bean in this instance. Write progress to * the console. * * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchMethodException */ public void testPropertiesToConsole() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { testPropertiesToConsole( true ); } /** * Code only to test this class while under construction: comment out thereafter as * an example. Requires a simple bean class for this purpose—any one will do. * * @param args - (unused). public static void main( String[] args ) { TestBean test = new TestBean( "BeanToTest", new BeanToTest() ); try { test.testPropertiesToConsole(); } catch( NoSuchMethodException e ) { System.out.println( "Missing setter/getter for this bean (shouldn't happen)" ); } catch( InvocationTargetException e ) { System.out.println( "Found a missing method" ); } catch( IllegalAccessException e ) { System.out.println( "No access to the bean being tested" ); } } */ @SuppressWarnings( "unchecked" ) private static final < X > X cast( Object o ) { return ( X ) o; } }
Here's the output from TestBean.main(). You get the log4j nasty-grams if you do not configure log4j properly:
log4j:WARN No appenders could be found for logger (com.etretatlogiciels.supplement.testing.TestBean).o = null (null) class = class com.etretatlogiciels.supplement.testing.BeanToTest log4j:WARN Please initialize the log4j system properly. kitty = cat (cat) x = 99 (99)
This approach tests any bean's getters and setters automatically. What it does or doesn't do is:
So, in the end, we've probably accomplished little more than to explore this issue. From here you should decide where you want to go in testing beans.
Yeah, this has been something of a waste of time. Its utility lies in walking you through the process to reach the conclusion that testing bean getters and setters is a waste of time unless they are really doing something, but let's not forget that turning a setter into a doer is a major no-no too.
I'm reaching the opinion that the following types of methods must be tested, but not getters and setters:
Last, you're not practicing TDD by writing tests for getters and setters because, by definition, you can't write them ahead of time. A trivial point, but certainly an answer to the newly persuaded TDD developer who quickly asks himself what the point to TDD was in the first place.