Home | FAQ | Contact me

On Testing Beans with JUnit

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.

Big caveat here

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?

Back to our question...

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.

Let's explore reflection

(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)

A useful utility class

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)

What's this automation strategy doing for us?

This approach tests any bean's getters and setters automatically. What it does or doesn't do is:

  1. The test is purely ontologicial: It only tests the existence of setters and getters. It will not test whether there should be any that haven't been coded or whether the ones that exist need to exist. Academically speaking, unless there is a need to support injection of every value in your bean, you should abstain even from writing setters or from revealing internal values by writing getters. This is also a debated topic.
  2.  
  3. It tests whether the getter is returning the same thing as was set by the setter. Actually, it doesn't do that because there's no way to inject discrete values into the tests as written. The bean's properties are whatever they were set to or default to (0, null, false, etc.) If you use Eclipse to generate the setters and getters, and you compile cleanly, then there will likely be no mistakes of this sort.
  4.  
  5. It will not test "real" values—unless you inject them by hand prior to calling TestBean.testProperties() which you cannot do unless you've provide some static injection methods. Hard to do, very ugly and destructive of the looks of your code.
  6.  
  7. Cascading from the previous pitfall is that this utility will not exercise the bounds you impose on bean properties, e.g.: whether a String may be null or zero-length. In fact, you may have noticed that in the automated testing, TestBean doesn't even know which field is a String and which an int. And your bean likely has more complex fields or properties than just String and int.

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.

But, we're here to do meaningful JUnit testing!

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:

Test-driven development (TDD)

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.

Links