Cartesian Plane Lesson 7 Page 5: Remaining Tests for the PropertyManager

Property Management, Testing

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.

Next: