Cartesian Plane Lesson 18 Page 14: The FontEditorDialog JUnit Test

FontEditor, FontEditorDialog, JUnit, GUI Testing

We will develop a JUnit test class on this page to validate the FontEditorDialog class we discussed on the previous page. To support that effort, we will also develop the FontEditorDialogTestGUI class to manage the FontEditorDialog under test. We’ll begin our discussion with the FontEditorDialogTestGUI class.

GitHub repository: Cartesian Plane Lesson 18

Previous page: Cartesian Plane Lesson 18 Page 13: The FontEditorDialog Class

Class FontEditorDialogTestGUI

Here, we will discuss the FontEditorDialogTestGUI, a support class to manage the GUI for our FontEditorDialog JUnit test. The class is found on the test source branch in the …test_utils package.

FontEditorDialogTestGUI Implementation

Following is a discussion of the FontEditorDialogTestGUI class.

FontEditorDialogTestGUI Infrastructure
Following is an annotated listing of the FontEditorDialogTestGUI class’s fields.

 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
public class FontEditorDialogTestGUI
{
    private static final Profile    baseProfile = new Profile();
    
    private final Profile           profile     = new Profile();
    private final GraphPropertySet  mainWindow  = profile.getMainWindow();
    private final FontEditorDialog  dialog      =
        new FontEditorDialog( null, profile.getMainWindow() );
    private final FontEditor        fontEditor  = dialog.getFontEditor();
    private final JComboBox<String> nameEditor  = 
        fontEditor.getNameCombo();
    private final JCheckBox         boldBox     = 
        fontEditor.getBoldToggle();
    private final JCheckBox         italicBox   = 
        fontEditor.getItalicToggle();
    private final ColorEditor       colorEditor =
        fontEditor.getColorEditor();
    private final JTextField        colorText   =
        colorEditor.getTextEditor();
    private final JButton   okButton        = getButton( "OK" );
    private final JButton   cancelButton    = getButton( "Cancel" );
    private final JButton   resetButton     = getButton( "Reset" );
    
    private Object  adHocObj   = null;
    // ...
}
  • Line 3: The baseProfile records the PropertyManager’s state at the start of the JUnit test. It is not modified after instantiation. It is used to restore the PropertyManager to its initial state each time a test method is executed. See resetProfile().
  • Line 5: This is the working Profile. At any time, it will reflect property values set by individual tests. The client can restore it to its initial state, as reflected by baseProfile, by invoking the resetProfile() method.
  • Line 6: This is the GraphPropertySetMW object from the working Profile.
  • Lines 7,8: The FontEditorDialog under test.
  • Line 9: This is the FontEditor encapsulated in the FontEditorDialog.
  • Lines 10-19: Components for editing font properties in the FontEditor GUI:
    • Lines 10,11: The name text field.
    • Lines 12,13, The bold checkbox.
    • Lines 14,15: The italic checkbox.,
    • Lines 16,17: The ColorEditor encapsulated in the FontEditor.
    • Lines 18,19: The color editor text field encapsulated in the ColorEditor.
  • Lines 20-22: The OK, Cancel, and Reset buttons from the FontEditorDialog under test.
  • Line 24: This is an object for transient use in individual methods. It is particularly convenient for use in lambdas.

FontEditorDialogTestGUI Private Methods
This section describes the private methods in the FontEditorDialogTestGUI class.

🟦 private void doClick( JButton button )
The doClick method selects the given button in the context of the EDT. It looks like this:

private void doClick( JButton button )
{
    GUIUtils.schedEDTAndWait( () -> button.doClick() );
}

🟦 private Object getProperty( Supplier<Object> getter )
Working in the context of the EDT, the getProperty method obtains a value from the given Supplier. The value is then returned. The code for this method follows.

private Object getProperty( Supplier<Object> getter )
{
    GUIUtils.schedEDTAndWait( () -> adHocObj = getter.get() );
    return adHocObj;
}

🟦 private String getStringProperty( Supplier<Object> getter )
🟦 private float getFloatProperty( Supplier<Object> getter )
🟦 private float getIntProperty( Supplier<Object> getter )
🟦 private float getBooleanProperty( Supplier<Object> getter )
Working in the context of the EDT, these methods obtain a value of type String, float, int, or boolean from the given supplier. The value is then returned. See also getProperty(). The implementation of these methods follows a common pattern; the code for the getStringProperty method follows. The code for all the methods is in the GitHub repository.

private String getStringProperty( Supplier<Object> getter )
{
    Object  objVal  = getProperty( getter );
    assertTrue( objVal instanceof String );
    return (String)objVal;
}

🟦 private void setProperty( Consumer<Object> setter, Object val )
Working in the context of the EDT, the setProperty method applies the given value via the given Consumer. Here’s the code.

private void setProperty( Consumer<Object> setter, Object val )
{
    GUIUtils.schedEDTAndWait( () -> setter.accept( val ) );
}

FontEditorDialogTestGUI Public Methods
Below, find a discussion of the FontEditorDialogTestGUI class’s public methods.

🟦 public Thread showDialog()
This method starts the dialog under test in a dedicated thread. It waits for the dialog to become visible and returns a reference to the dedicated thread. Following is a listing of the showDialog method.

public Thread showDialog()
{
    Runnable    runner  = 
        () -> GUIUtils.schedEDTAndWait( dialog::showDialog );
    Thread  thread  = new Thread( runner );
    thread.start();
    Utils.pause( 125 );
    return thread;
}

🟦 public void resetProfile()
The resetProfile method returns the PropertyManager and working Profile to their initial states. It looks like this.

public void resetProfile()
{
    baseProfile.apply();
    profile.reset();
}

🟦 public void clickOK()
🟦 public void clickCancel()
🟦 public void clickReset()
These methods select the named button in the context of the EDT. The code for clickOK follows. The other methods follow a similar pattern and can be found in the GitHub repository.

public void clickOK()
{
    doClick( okButton );
}

🟦 public Profile getProfile()
🟦 public GraphPropertySet getPropertySet()
These methods return the Profile from the FontEditorDialog under test and the GraphPropertySetMW from the Profile, respectively. The getPropertySet method obtains the target value in the context of the EDT. Here’s the code.

public Profile getProfile()
{
    return profile;
}
public GraphPropertySet getPropertySet()
{
    Object  value   = getProperty( () -> dialog.getPropertySet() );
    assertNotNull( value );
    assertTrue( value instanceof GraphPropertySet );
    return (GraphPropertySet)value;
}

🟦 public String getProfileName()
🟦 public String getProfileSize()
🟦 public boolean isProfileBold()
🟦 public boolean isProfileItalic()
These methods get the values of the font name, font size, bold, and italic properties from the Profile encapsulated in the FontEditorDialog under test. Interrogation of the Profile is performed on the EDT. Here’s the code for getProfileName; the code for the other methods follows a similar pattern and can be found in the GitHub repository. See also getProfileColor().

public String getProfileName()
{
    String  name    = 
        getStringProperty( () -> mainWindow.getFontName() );
    return name;
}

🟦 public int getProfileColor()
The getProfileColor method obtains the RGB value of the font color from the encapsulated Profile. The interrogation is executed on the EDT. I’m listing this method separately because it uses an unusual (for us) lambda. The variable rgb2 is declared inside the anonymous class (the body of the lambda expression). The statement return rgb2 makes rgb2 the value of the lambda expression, which is then assigned to the variable rgb declared directly in the getProfileColor method. The code looks like this:

public int getProfileColor()
{
    int     rgb     = getIntProperty( () -> {
        Color   color   = mainWindow.getFGColor();
        int     rgb2    = color.getRGB() & 0xFFFFFF;
        return rgb2;
    });
    return rgb;
}

🟦 public String getName()
🟦 public String getSize()
🟦 public boolean isBold()
🟦 public boolean isItalic()
These methods get the values of the font name, font size, bold, and italic properties from the FontEditor encapsulated in the FontEditorDialog under test. The FontEditor is interrogated on the EDT. Here’s the code for getName; the code for the other methods follows a similar pattern and can be found in the GitHub repository. See also getColor().

public String getName()
{
    String  name    = 
        getStringProperty( () -> fontEditor.getName() );
    return name;
}

🟦 public int getColor()
The getColor method obtains the RGB value of the font color from the encapsulated FontEditor GUI. I’m listing this method separately because it’s a little more involved than getName et al. (above). The interrogation is executed on the EDT. A value of -1 is returned if the value obtained from the GUI component does not contain a valid RGB value. The code for this method follows.

public int getColor()
{
    int     rgb     = -1;
    String  text    = 
        getStringProperty( () -> colorText.getText() );
    try
    {
        rgb = Integer.decode( text );
    }
    catch ( NumberFormatException exc )
    {
        // deliberately ignored
    }
    return rgb;
}

🟦 public void setName( String name )
🟦 public void setSize( int size )
🟦 public void setColor( int rgbVal )
🟦 public void setBold( boolean selected )
🟦 public void setItalic( boolean selected )
These methods set the values of the components responsible for editing the font name, font size, font color, bold, and italic properties. Operations are executed in the context of the EDT. Following is the code for setName and setColor; the code for the other methods follows a pattern similar to that of setName.

public void setName( String name )
{
    setProperty( o -> nameEditor.setSelectedItem( o ), name );
}
public void setColor( int rgbVal )
{
    String  strVal  = String.valueOf( rgbVal );
    setProperty( o -> colorText.setText( (String)o ), strVal );
}

Class FontEditorDialogTest

The JUnit test class for the FontEditorDialog concentrates on the behavior initiated by pushing one of the control buttons, OK, Reset, and Cancel:

  • OK Button
    When this button is pressed, property values from the GUI must be transferred to the encapsulated Profile, and the dialog must be closed.
  • Reset Button
    When this button is pressed, property values from the encapsulated Profile must be transferred to the GUI, overwriting any changes made by the operator. The dialog must remain open.
  • Cancel Button
    When this button is pushed, property values from the GUI must be discarded; no changes are made to the encapsulated Profile. The GUI must be closed.

Following is a discussion of the FontEditorDialogTest class implementation.

FontEditorDialogTest Implementation

Most of our testing will be based on setting a property in the GUI, pushing one of the dialog control buttons (OK, Reset, Cancel), and then validating the dialog’s behavior. The testSize() method, which validates the dialog behavior when the size property value is modified, is a good example of this strategy. It is a parameterized method:
    @ParameterizedTest
    @ValueSource( booleans= { true, false} )
    public void testSize( boolean okay )

The parameter, okay, tells the test whether to conclude by pushing the OK button (okay=true) or Cancel button (okay=false). Here’s the pseudocode.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
verify that the dialog is not visible
start the dialog

verify that the size value in the profile correctly matches the size property in the GUI

set a new size in the GUI
verify that the size in the profile has not changed

click the reset button
verify that the size property in the GUI has returned to its original value

set the new size in the GUI
if ( okay )
    click OK button
    verify that the new size from the GUI has  been copied to the profile
else
    click Cancel button
    verify that the size in the profile has not changed
verify that the dialog is closed

FontEditorDialogTest Infrastructure
The FontEditorDialogTest class doesn’t have much infrastructure. It has one field to encapsulate the test GUI:
    private final FontEditorDialogTestGUI testGUI =
        new FontEditorDialogTestGUI();

It has a @BeforeEach method that resets the test GUI before executing a test method:
    testGUI.resetProfile();

It has one private method, described below.

🟦 private void testBoolean(boolean okay, Consumer<Boolean> guiSetter, Supplier<Boolean> guiGetter, Supplier<Boolean> profileGetter)
The sole private method is testBoolean, which encapsulates the common code for testing the bold and italic properties. Here is an annotated listing of this method.

 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
private void testBoolean( 
    boolean           okay,
    Consumer<Boolean> guiSetter, 
    Supplier<Boolean> guiGetter,
    Supplier<Boolean> profileGetter
 )
{
    assertFalse( testGUI.isVisible() );
    Thread  guiThread   = testGUI.showDialog();
    
    boolean origProfileVal  = profileGetter.get();
    boolean origGUIVal      = guiGetter.get();
    boolean newVal          = !origProfileVal;
    assertEquals( origProfileVal, origGUIVal );
    assertNotEquals( origProfileVal, newVal );
    
    guiSetter.accept( newVal );
    assertEquals( newVal, guiGetter.get() );
    assertEquals( origProfileVal, profileGetter.get() );
    
    testGUI.clickReset();
    assertEquals( origProfileVal, profileGetter.get() );
    assertEquals( origProfileVal, guiGetter.get() );
    
    guiSetter.accept( newVal );
    assertEquals( newVal, guiGetter.get() );
    assertEquals( origProfileVal, profileGetter.get() );
    
    if ( okay )
        testGUI.clickOK();
    else
        testGUI.clickCancel();
    Utils.join( guiThread );
    assertFalse( testGUI.isVisible() );
    if ( okay )
        assertEquals( newVal, profileGetter.get() );
    else
        assertEquals( origProfileVal, profileGetter.get() );
}
  • Lines 1-6: Method declaration:
    • Line 2: The okay parameter determines whether or not the test concludes by pushing the OK or Cancel button.
    • Line 3: This Consumer tells the method how to set the target Boolean value in the GUI, for example:
          testGUI::setBold
    • Line 4: This Supplier tells the method how to obtain the property’s current Boolean value from the GUI, for example:
          testGUI::isBold
    • Line 4: This Supplier tells the method how to obtain the property’s current Boolean value from the working Profile, for example:
          testGUI::isProfileBold
  • Line 8: Verify that the dialog under test is not visible.
  • Line 9: Post the dialog under test; see also FontEditorDialogTestGUI.showDialog().
  • Line 11: Get the starting property value from the Profile.
  • Line 12: Get the starting property value from the GUI.
  • Line 13: Calculate a distinct property value for testing.
  • Line 14: Verify that the starting Profile and GUI values agree.
  • Line 15: Sanity check: verify that the test value differs from the starting value.
  • Line 17: Set the new property value in the GUI.
  • Line 18: Verify that the GUI reflects the new value.
  • Line 19: Verify that the property value in the Profile has not changed.
  • Line 21: Click the Reset button.
  • Line 22: Verify that the property value in the Profile has not changed.
  • Line 23: Verify that the property value in the GUI has returned to its original value.
  • Line 25: Set the new property value in the GUI.
  • Line 26: Verify that the new property value has been set in the GUI.
  • Line 27: Verify that the property value in the Profile has not changed.
  • Lines 29-32: Select the OK or Cancel button as indicated by the okay parameter.
  • Line 33: Wait for the dialog to close.
  • Line 34: Verify that the dialog has closed.
  • Lines 35-38: If the OK button was pushed, verify that the property value has been copied from the GUI to the Profile; otherwise, verify that the property value in the Profile has not changed.

FontEditorDialogTest Public Methods
The following section describes the FontEditorDialogTest’s principle test methods.

🟦 @Test public void testGetPropertySet()
This is a test for the getPropertySet method in the FontEditorDialog. It gets the working Profile from the test GUI and the GraphPropertySet from the Profile (expPropertySet). The GraphPropertySet from the Profile is expected to be identical to the GraphPropertySet contained in the dialog under test (actPropertySet; see also FontEditorDialogTestGUI-Infrastructure lines 5-8 and FontEditorDialogTestGUI.getPropertySet(). The assertSame method tests for the identity of two values, e.g., assertSame(A, B) is the same as assertTrue(A == B). Here’s the code for testGetPropertySet.

@Test
public void testGetPropertySet()
{
    Profile             testProfile     = testGUI.getProfile();
    GraphPropertySet    expPropertySet  = testProfile.getMainWindow();
    GraphPropertySet    actPropertySet  = testGUI.getPropertySet();
    assertSame( expPropertySet, actPropertySet );
}

🟦 @ParameterizedTest public void testBold( boolean okay )
🟦 @ParameterizedTest public void testItalic( boolean okay )
These parameterized tests verify that the dialog’s OK and Cancel buttons work correctly after changing the FontEditor’s bold and italic properties. If the parameter is true, the OK button is tested; otherwise, the Cancel button is tested. See also testBoolean above. Following is the testItalic code; the testBold implementation is an exercise for the student; the solution is in the GitHub repository.

@ParameterizedTest    
@ValueSource( booleans= {true, false} )
public void testItalic( boolean okay )
{
    testBoolean( 
        okay, 
        testGUI::setItalic, 
        testGUI::isItalic, 
        testGUI::isProfileItalic
    );
}

🟦 @ParameterizedTest public void testName( boolean okay )
🟦 @ParameterizedTest public void testSize( boolean okay )
🟦 @ParameterizedTest public void testColor( boolean okay )
These parameterized tests verify that the dialog’s OK and Cancel buttons work correctly after changing the FontEditor’s name, size, and color properties. If the parameter is true, the OK button is tested; otherwise, the Cancel button is tested. Testing follows the same strategy as the testSize pseudocode and the testBoolean method. Following is the testName code; implementing the remaining methods is an exercise for the student; the solution is in the GitHub repository.

@ParameterizedTest    
@ValueSource( booleans= { true, false} )
public void testName( boolean okay )
{
    assertFalse( testGUI.isVisible() );
    Thread  guiThread   = testGUI.showDialog();
    
    String  origProfileName = testGUI.getProfileName();
    String  origGUIName     = testGUI.getName();
    String  newName         = origProfileName.equals( Font.DIALOG ) ? 
        Font.MONOSPACED : Font.DIALOG;
    assertEquals( origProfileName, origGUIName );
    assertNotEquals( origProfileName, newName );
    
    testGUI.setName( newName );
    assertEquals( newName, testGUI.getName() );
    assertEquals( origProfileName, testGUI.getProfileName() );
    
    testGUI.clickReset();
    assertEquals( origProfileName, testGUI.getName() );
    assertEquals( origProfileName, testGUI.getProfileName() );
    
    testGUI.setName( newName );
    assertEquals( newName, testGUI.getName() );
    assertEquals( origProfileName, testGUI.getProfileName() );
    
    if ( okay )
        testGUI.clickOK();
    else
        testGUI.clickCancel();
    Utils.join( guiThread );
    assertFalse( testGUI.isVisible() );
    if ( okay )
        assertEquals( newName, testGUI.getProfileName() );
    else
        assertEquals( origProfileName, testGUI.getProfileName() );
}

Summary

On this page, we concluded our discussion of the FontEditorDialog class by implementing FontEditorDialogTest, a JUnit test class that validates it. We supplemented the JUnit test class by writing FontEditorDialogTestGUI to manage the dialog under test. We are now ready to use the FontEditorDialog by incorporating it into the ProfileEditor, a GUI that allows the operator to edit property values in a Profile.

Next: The ProfileEditor