This is the conclusion of the lesson about property management. In this lesson, we will complete the unit tests for the PropertyManager class. On the previous page of this lesson, we tested the PropertyManager’s ability to obtain property values from a hierarchy of sources: command line, environment, user ini file, or application ini file. On this page, we’ll test the remaining facilities in PropertyManager, such as addPropertyChangeListener and asFloat.
GitHub repository: Cartesian Plane Part 7b
Previous lesson: Cartesian Plane Lesson 7 Page 4: Testing the PropertyManager
The PropertyManagerTest Class
Here is a summary of the facilities we will validate using the PropertyManagerTest test class.
- The as methods: asInt, asFloat, asBoolean, etc.
- The setProperty( String, type ) overloads, setProperty( String, int), setProperty( String, float), etc.
- Adding property and per-property listeners
- Removing property and per-property listeners
Below, we’ll discuss the details of the PropertyManagerTest class, beginning with the infrastructure.
PropertyManagerTest Infrastructure
The PropertyManagerTest class has one instance field and one private nested class. The instance field is shorthand for PropertyManager.INSTANCE, and is declared as a field for convenience: private final PropertyManager pmgr = PropertyManager.INSTANCE;
The nested class, TestListener, implements PropertyChangeListener. Its purpose is to assist in testing the add and remove property listeners. All it does is catch a PropertyChangeEvent and record its state. It has four instance fields for convenient access to the essential data passed in a PropertyChangeEvent:
private static class TestListener implements PropertyChangeListener
{
private Object source = null;
private Object oldVal = null;
private Object newVal = null;
private String name = null;
// ...
}
Its propertyChange method (required because it implements PropertyChangeListener) initializes these fields from its PropertyChangeEvent parameter:
@Override
public void propertyChange( PropertyChangeEvent evt )
{
source = evt.getSource();
oldVal = evt.getOldValue();
newVal = evt.getNewValue();
name = evt.getPropertyName();
}
And it has getters for its four fields:
public Object getSource()
{
return source;
}
public Object getOldVal()
{
return oldVal;
}
public Object getNewVal()
{
return newVal;
}
public String getName()
{
return name;
}
Note that when we set a property‘s value, the property name is not required to be declared in CPConstants. In fact, for convenience, I’ve made up a lot of property names, such as phonyFloatProp and notAValidPropertyName.
One final note before we get started: most of the getter tests (for asInt, asFloat, etc.) are parameterized tests, using the @ParameterizedTest and @ValueSource JUnit tags. We discussed these in the last lesson.
PropertyManagerTest Test Methods
Following is a discussion of the test methods in the PropertyManagerTest class.
⬛ public void testAddPropertyChangeListener()
This method adds a property listener to the PropertyManager. It changes a property and verifies that the property listener is correctly notified of the change. Here’s an annotated listing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Test public void testAddPropertyChangeListener() { String propName = CPConstants.TIC_MAJOR_WEIGHT_PN; float expOldVal = pmgr.asFloat( propName ); float expNewVal = expOldVal + 1; TestListener listener = new TestListener(); pmgr.addPropertyChangeListener( listener ); pmgr.setProperty( propName, expNewVal ); float actNewValue = pmgr.asFloat( propName ); assertEquals( expNewVal, actNewValue ); assertNotEquals( expOldVal, actNewValue ); float actOldVal = Float.parseFloat( (String)listener.getOldVal() ); float actNewVal = Float.parseFloat( (String)listener.getNewVal() ); assertEquals( expOldVal, actOldVal ); assertEquals( expNewVal, actNewVal ); assertEquals( pmgr, listener.getSource() ); assertEquals( propName, listener.getName() ); } |
- Line 4: The name of the property we’re going to change.
- Line 5: The original value of the property.
- Line 6: The new value we’re going to assign to the property,
- Line 7: The PropertyChangeListener to catch the property change event.
- Lines 9,10: Register the PropertyChangeListener and change the target property.
- Lines 12-14: Sanity check; verify that the property was changed and that the original and new values differ.
- Lines 16-19: Verify that the original and new values reported to the PropertyChange listener are correct.
- Line 20: Verify that the PropertyManager is declared as the source of the event.
- Line 21: Verify that the property name identified in the PropertyChangeEvent object is correct.
⬛ public void testRemovePropertyChangeListener()
Add a property change listener, change a property, and verify that it correctly catches a PropertyChangeEvent; remove the property listener, change a property, and verify that the listener does not catch the event. An annotated listing of this method follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @Test public void testRemovePropertyChangeListener() { String prop1 = CPConstants.TIC_MAJOR_WEIGHT_PN; float prop1OldVal = pmgr.asFloat( prop1 ); float prop1NewVal = prop1OldVal + 1; TestListener listener = new TestListener(); pmgr.addPropertyChangeListener( listener ); pmgr.setProperty( prop1, prop1NewVal ); // Verify that the listener fired as expected float actOldVal = Float.parseFloat( (String)listener.getOldVal() ); float actNewVal = Float.parseFloat( (String)listener.getNewVal() ); assertEquals( prop1OldVal, actOldVal ); assertEquals( prop1NewVal, actNewVal ); assertEquals( prop1, listener.getName() ); // Remove the listener and verify that it does NOT fire // when a property is changed pmgr.removePropertyChangeListener( listener ); String prop2 = CPConstants.GRID_UNIT_PN; float prop2OldVal = pmgr.asFloat( prop2 ); float prop2NewVal = prop2OldVal + 1; assertNotEquals( prop2OldVal, prop2NewVal ); assertNotEquals( prop1, prop2 ); assertNotEquals( prop1NewVal, prop2NewVal ); pmgr.setProperty( prop2, prop2OldVal ); actOldVal = Float.parseFloat( (String)listener.getOldVal() ); actNewVal = Float.parseFloat( (String)listener.getNewVal() ); assertEquals( prop1OldVal, actOldVal ); assertEquals( prop1NewVal, actNewVal ); assertEquals( prop1, listener.getName() ); } |
- Lines 4-6: The name of the first property we will change and its old and new values.
- Line 7: The event listener we will use for both property changes.
- Lines 9,10: Register the event listener and change the first property.
- Lines 17-19: Verify that the event listener caught the event; verify that the content of the accompanying event object is correct.
- Line 23: Remove the property change listener.
- Lines 24-26: The name of the second property to change and its old and new values.
- Lines 28-30: Sanity check:
- Line 28: The old and new values of the second property must be different.
- Line 29: The names of the first and second properties must differ.
- Line 30: The new values of the first and second properties must differ.
- Line 31: Change the value of the second property.
- Lines 34-38: Verify that the listener’s state has not changed.
⬛ public void testAddPerPropertyChangeListener()
Add a per-property change listener for two different properties. Change one of those properties and verify that the correct listener catches the event and the other listener does not. The annotated listing follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @Test public void testAddPerPropertyChangeListener() { String prop1 = CPConstants.TIC_MAJOR_WEIGHT_PN; float prop1OldValue = pmgr.asFloat( prop1 ); float prop1NewValue = prop1OldValue + 1; TestListener prop1Listener = new TestListener(); pmgr.addPropertyChangeListener( prop1, prop1Listener ); String prop2 = CPConstants.TIC_MINOR_WEIGHT_PN; TestListener prop2Listener = new TestListener(); pmgr.addPropertyChangeListener( prop2, prop2Listener ); pmgr.setProperty( prop1, prop1NewValue ); float actOldVal = Float.parseFloat( (String)prop1Listener.getOldVal() ); float actNewVal = Float.parseFloat( (String)prop1Listener.getNewVal() ); assertEquals( prop1, prop1Listener.getName() ); assertEquals( prop1NewValue, actNewVal ); assertEquals( prop1OldValue, actOldVal ); assertEquals( pmgr, prop1Listener.getSource() ); assertNull( prop2Listener.getName() ); assertNull( prop2Listener.getNewVal() ); assertNull( prop2Listener.getOldVal() ); assertNull( prop2Listener.getSource() ); } |
- Lines 4-6: The name and expected old and new values for the first property.
- Lines 7,8: Create a listener and register it as a per-property listener for the first property.
- Lines 10-13: Register a per-property listener for a second property.
- Line 14: Change the value of the first property.
- Lines 16-23: Verify that the listener for the first property fired.
- Lines 25-28: Verify that the listener for the second property did not fire.
⬛ public void testRemovePerPropertyChangeListener()
Add a per-property change listener, change the property, and verify that it correctly catches a PropertyChangeEvent; remove the per-property listener, change the property a second time, and verify that the listener does not catch an event. Implementation of this method is an exercise for the student. The solution is in the GitHub repository.
⬛ @ParameterizedTest
@ValueSource( ints={ 5, 10, 15, 20 } )
public void testIntAccessors( int expVal )
This parameterized test validates the asInt and setProperty(String, int) methods. Here is a listing of the test.
1 2 3 4 5 6 7 8 9 | @ParameterizedTest @ValueSource( ints={ 5, 10, 15, 20 } ) void testIntAccessors( int expVal ) { String propName = "phonyIntProp"; pmgr.setProperty( propName, expVal ); int actVal = pmgr.asInt( propName ); assertEquals( expVal, actVal ); } |
⬛ public void testFloatAccessors( float expVal )
⬛ public void testBooleanAccessors( boolean expVal )
⬛ public void testStringAccessors( String expVal )
These are parameterized tests for the asFloat(String), setProperty(String,float), asBoolean(String), setProperty(String,boolean), asString(String), and setProperty(String,String) methods. They are similar to asInt(String) and setProperty(String,int). Their implementations are an exercise for the student. The solutions are in the GitHub repository.
⬛ @ParameterizedTest
@ValueSource(strings={ “0xFF0000”, “0x00FF00”, “0x0000FF”, “#FF00FF” } )
public void testColorAccessors( String strVal )
Tests the asColor(String) and setProperty(String, Color) methods. As we’ve seen, testing methods that get and set colors are a little awkward, so this method differs slightly from the previous accessor test methods. Here is the listing.
1 2 3 4 5 6 7 8 9 10 11 | @ParameterizedTest @ValueSource(strings={ "0xFF0000", "0x00FF00", "0x0000FF", "#FF00FF" } ) void testColorAccessors( String strVal ) { String propName = "phonyColorProp"; int intColor = Integer.decode( strVal ); Color expColor = new Color( intColor ); pmgr.setProperty( propName, expColor ); Color actColor = pmgr.asColor( propName ); assertEquals( expColor, actColor ); } |
⬛ public void testFontStyleAccessor()
Tests the asFontStyle(String) method. This one is also different from other similar test methods. It doesn’t easily lend itself to parameterized testing. Also, we have some exception handling to test (lines 15-18 of the accompanying code). The listing follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Test void testFontStyleAccessor() { String propName = "phonyFontStyleProp"; pmgr.setProperty( propName, "PLAIN" ); assertEquals( Font.PLAIN, pmgr.asFontStyle( propName ) ); pmgr.setProperty( propName, "ITALIC" ); assertEquals( Font.ITALIC, pmgr.asFontStyle( propName ) ); pmgr.setProperty( propName, "BOLD" ); assertEquals( Font.BOLD, pmgr.asFontStyle( propName ) ); Class<IllegalArgumentException> clazz = IllegalArgumentException.class; pmgr.setProperty( propName, "INVALID_FONT_STYLE" ); assertThrows( clazz, () -> pmgr.asFontStyle( propName ) ); } |
Summary
So, that’s it for our lengthy look at testing. I hope you have a feeling for how important it is and how much time and effort it takes. Also, remember that if you’re programming in Java, JUnit is a terrific resource that can make your life much easier.