devxlogo

JUnit and Its Extensions Make Unit Testing Your Java Code Much Easier

JUnit and Its Extensions Make Unit Testing Your Java Code Much Easier

erhaps the most popular Java open-source tool available today is JUnit. Originally developed by Kent Beck and Eric Gamma for use in Extreme Programming, JUnit has rapidly become a standard part of Java developers’ professional lives. Martin Fowler wrote: “Never in the field of software engineering has so much been owed by so many to so few lines of code.” And he was absolutely right.

This article focuses on some of the issues you’ll face once you’ve learned the basic operational facts of JUnit and start writing real tests. It also introduces a few extensions to JUnit that will make your life a lot easier.

Author’s Note: If you haven’t already, you’ll need to download the latest version of JUnit and at least run through the excellent FAQ that accompanies it. It will show you how to setup JUnit and write basic tests. At the time I wrote this article, the latest version of JUnit was 3.8.1. That’s the version I use throughout.

JUnit-Addons
The first JUnit extension framework that we’ll examine is called JUnit-Addons, which at the time of writing was at version 3.7.1. Among its other features, JUnit-Addons solves two awkward problems for Java developers aiming to test their code:

  1. How to check the contents of two arrays for equality
  2. How to access private members

The ability to do both of these things is a very common requirement, but they aren’t mandatory for all projects so they were relegated to an extension. Let’s examine each problem and see how JUnit-Addons can solve it.

How Do I Check the Contents of Two Arrays for Equality?
In JUnit, an assertion performs an individual test (not to be confused with the new assertion keyword introduced in JDK1.4). The most common assertions are perhaps equality assertions. These compare two values?the test passes if they’re equal and fails if they’re not. The idea is to have the assertion compare test data with the data the class being tested generates.

All JUnit assertions take either primitives or objects as their arguments. The lack of array-type parameters means that if two arrays are compared they’ll be treated as references, and only the reference values will be compared. This is rarely the behavior that a developer is looking for as it usually causes a test to fail!

JUnit-Addons solves this issue by providing a suite of static methods on its ArrayAssert class, which take arrays of primitives and objects as parameters. When testing two arrays, it iterates through them and compares their contents (as you would expect).

The following simple example illustrates how you can write such a test:

public void testArrays() {  String[] testData = {"one", "two", "three", "four", "five"};  Fragment fragment = new Fragment();  ArrayAssert.assertEqualArrays(testData, fragment.getNumbers());}

To use a test like this you’d replace Fragment with the class being tested. Of course, the method getNumbers() must return an array. The bold text indicates where JUnit-Addons was used.

At this time, multidimensional arrays still are a problem. Rather than treat these as arrays of arrays, JUnit-Addons simply compares references. This is a problem that’s currently being worked on by the JUnit community and hopefully an updated version of the library that provides this feature will be available soon.

How Do I Access Private Members?
The need to test private members of an object most commonly arises when following the Test-Driven-Development (TDD) process for writing code. In this process, the developer writes a simple test, implements code to make it pass, and refactors before moving on to the next simple test.

One of the consequences of TDD is that methods get built up slowly in a number of very small iterations. This applies to private methods as well as public methods, and so you need a way to incrementally test private methods.

If your approach is to develop code before writing the tests for it, then you can infer the correct behavior of private methods through the behavior of public methods and attributes. However, even in this process, having the ability to test a private method or attribute is sometimes useful.

Not many programmers know that reflection can, in certain cases, completely bypass encapsulation and provide access to an object’s otherwise encapsulated members. JUnit-Addons uses this fact to provide a few simple methods for testing methods and attributes on an object or class (static) level.

The following code fragment illustrates how to use these features to write a test that accesses a private vector at class level (static):

public void testVectorSize() throws NoSuchFieldException {  Thing dummy1 = Factory.createThing();  Thing dummy2 = Factory.createThing();  Vector things = (Vector) PrivateAccessor.getField(Factory.class, "things");  assertEquals(2, things.size());}

This is part of a suite of tests on a factory class (according to Erich Gamma, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.). The Factory class is responsible for creating instances of Thing. It also maintains a vector (called things) of references to each Thing it instantiates. This test simply confirms that the vector contains the correct number of elements. The bold text shows where you use JUnit-Addons to access the private state of the factory class. The PrivateAccessor class provides four static methods for accessing private members: two each for objects and classes (static), providing facilities to test both methods and?as shown above?attributes/fields.jfcUnit Tests Swing GUIs
Swing is difficult to test. It’s big and complicated, and GUIs change frequently during development. To simplify things, you should ensure that you always separate business logic from presentation logic. The most common way to do this is to implement the MVC pattern (Erich Gamma, et al.). MVC allows you to concentrate your testing on the model and the controller, which are significantly easier to test.

However, the GUI still needs to be tested. That’s where the JUnit extension called jfcUnit comes in?it’s designed to test Swing GUIs. Testing a Swing GUI is traditionally hard for two reasons:

  1. Tests can fail if a visual component is moved to a different part of the screen or onto a different panel in the same window.
  2. Tests can be extremely difficult to write because of the difficulty in locating visual components to test.

jfcUnit does a good job of resolving both these issues, but testing GUIs is still time consuming and laborious (see the Sidebar: GUIs: To Test or Not to Test?). A common suggestion is test the GUI only if it contains complicated presentation logic. This means that if you’re writing a basic input form it’s probably not worth testing?it’s too trivial and is unlikely to be wrong.

For this example, I’ll examine a basic Logon form. Figure 1 shows a JFrame, which contains two JTextArea components, a JLabel to identify each of them to the user, and two JButton components.

Figure 1: A JFrame with two JTextArea components

This is a fairly trivial form, but the techniques I’ll examine to test it are applicable to most Swing GUI forms. Before I go any further into the creation of the tests for this, however, I need to discuss fixtures.

Fixtures Ensure a Clean Test Environment
A fixture is a set of conditions that are true for every test in a single suite (a suite is all the tests in a single test class). JUnit uses setUp() and tearDown() methods to create and reset a fixture.

In this article’s example, you need to have a few private attributes for your tests to access:

private JFCTestHelper helper = null;private Main main = null;private JButton ok = null;private JButton cancel = null;private JTextField username = null;private JTextField password = null;

The helper object is used throughout jfcUnit tests to access elements of a form and to simulate mouse and keyboard events. These attributes are populated in the fixture’s setup() method:

protected void setUp() throws Exception {  helper = new JFCTestHelper();  main = new Main();  main.show();  ok = (JButton) helper.findNamedComponent("Ok", main, 0);  cancel = (JButton) helper.findNamedComponent("Cancel", main, 0);  username = (JTextField) helper.findNamedComponent("username", main, 0);  password = (JTextField) helper.findNamedComponent("password", main, 0);  flushAWT();} 

The setUp() method here finds the active components on the form and assigns their references to the instance attributes: ok, cancel, username, and password.

The way jfcUnit finds components is quite clever. It calls the findNamedComponent() method, like in the following line:

  cancel = (JButton) helper.findNamedComponent("Cancel", main, 0);

This method then checks jfcUnit’s internal collection of component references. jfcUnit builds this collection by adding itself as an event listener to all component and hierarchy events for the GUI. Whenever a component is added to a form jfcUnit responds to the event by storing a reference to that component. So, when you call the findNamedComponent() method, it doesn’t need to perform the difficult task of navigating a form’s containers, it just returns the component from its internal store.

The tearDown() method resets the test’s attributes back to null and destroys the form:

protected void tearDown() throws Exception {  ok = null;  cancel = null;  username = null;  password = null;  main.hide();  main = null;  flushAWT();}

This is necessary because you want the form to be as new for each test in your test class. The setUp() method is run before every test method, and the tearDown() method is run after every test method. In this example, it means that a new form is available for each test method.

Using these two methods carefully will help ensure that no dependencies exist between tests. Dependencies make tests more difficult to write. If you use fixtures to ensure a clean environment for each test, they have to be written independently. You want to create tests that are easy to change. If you have dependencies between the tests, when you change one you’d have to change all the dependant ones too?which is a bad thing.

Also, using fixtures tends to make the tests more readable and improves their design. Don’t forget, tests are code too?they need to be well designed and well refactored in order to maximize benefits and increase quality and productivity.Start Writing Your Tests
Now that your fixtures are in place and you’ve got direct access to the active components on the form, you can go ahead and start writing tests.

Do the Necessary Components Exist?
The findNamedComponent() method on helper, an instance of JFCTestHelper, does exactly what it says: it finds a component with a given name on a given form. This means that you have to name your components. Although you don’t usually do this when coding a GUI by hand, it’s a good practice?and it’s inexpensive. Of course when you use a GUI Builder, naming components is normal.

This is one of the rare instances when changing the code to facilitate a test costs very little, and it doesn’t really affect the quality of the code you’re testing.

You’re going to perform the following six tests on this form:

  1. Test that all the necessary visual components exist on the form.
  2. Test that the form appears with blank input fields.
  3. Test that an error dialog appears if no username and password exists.
  4. Test that an error dialog appears if no username exists.
  5. Test that an error dialog appears if no password exists.
  6. Check that a processing dialog appears if both the username and password are provided.

You’ll need to perform a number of other tests too, but I’ll leave those as an exercise for you. These tests confirm the form’s behavior under the following conditions:

  1. Invalid password
  2. Invalid username
  3. Cancel is pressed
  4. Valid username and password

The first test using the components found in the fixture only checks that they exist:

public void testLogonLayout() throws Exception {  assertNotNull(ok);  assertNotNull(cancel);  assertNotNull(username);  assertNotNull(password);}

At this level, where these components are or how they look isn’t important – that information can change rapidly as you lay out the GUI. You’re only interested in whether they exist. This gives you the freedom to decorate the GUI in many ways?as long as your components exist.

This simple test performs two very important functions:

  1. It confirms that all the components you need are actually present on the form.
  2. It confirms that your tests are working.

Having a broken test can lead to a lot of unnecessary work on production code that may otherwise be fault-free. If you run your form and see the necessary components but this test still fails, something may be wrong with the test.

The next test simply checks that the form is initialized with blank input fields:

public void testInitialState() {  assertEquals("", username.getText());  assertEquals("", password.getText());}

Again, this is a fairly simple yet vital test. You don’t want a lazy programmer to initialize these fields with data and then forget to take it out when you ship the product.

Do You Have the Correct Dialog?
Now the tests get more interesting. The next four tests check that the correct dialog box pops up when a user presses the OK button. The four conditions that need to be checked are:

  1. Only a username
  2. Only a password
  3. Neither
  4. Both

To do this, you need a way of setting the text in the text boxes. Because you have direct references to the components, you could just call the setText() method on them. This wouldn’t be a good test though, because it isn’t driving the GUI in precisely the same way a user would. jfcUnit’s helper allows you to send keystrokes to a form to simulate user interaction. Because you need to do this frequently during this example, I’ve created a private method to make it easier:

private void setText(JTextField field, String text) throws Exception {  field.requestFocus();  helper.sendString(new StringEventData(this, field, text));}

Now that you can set a field’s value you can use this method to test the form. Again, because you’re basically doing the same thing three times, I’ve written a method that uses setText(), presses the OK button, and asserts that the correct dialog is showing:

private void checkDialogAppears(String expected, String usernameText, 
String passwordText) throws Exception { setText(username, usernameText); setText(password, passwordText); helper.enterClickAndLeave(new MouseEventData(this, ok)); List showingDialogs = helper.getShowingDialogs(main); assertEquals(1, showingDialogs.size()); JDialog dialog = (JDialog) showingDialogs.get(0); assertEquals(expected, dialog.getTitle()); helper.disposeWindow(dialog, this);}

This is sometimes called a check method. The JUnit framework offers no direct support for them, but they’re common anyway. Developers use them whenever they need to abstract commonality out of a set of test methods, and that commonality includes the assertion itself.

The checkDialogAppears() method accepts as parameters the title of the dialog and the values for the username and password. It uses the helper’s enterClickAndLeave() method to simulate the mouse clicking the OK button. jfcUnit’s methods are well named, so the rest of the method should be fairly easy to read. When requesting the showing dialogs a list is returned because there may well be more than one. In this case, you’re expecting only one so you assert that this is the case. Then you obtain the title text and assert that it is what you expected.

You use this method in your tests:

public void testAcceptWithBlankUsername() throws Exception {  checkDialogAppears("Logon Error: username", "", "admin");}public void testAcceptWithBlankPassword() throws Exception {  checkDialogAppears("Logon Error: password", "admin", "");}public void testAcceptWithBlankFields() throws Exception {  checkDialogAppears("Logon Error: username/password", "", "");}public void testProcessingLogon() throws Exception {  checkDialogAppears("Logging on", "admin", "admin");}

The tests that I have left as exercises should be quite simple for you to write now, as they’re primarily variations on the ones above.

You don’t have to write complex tests for authentication on the GUI. If you’ve followed the MVC pattern, you should have your authorization services in an independent module with its own tests. This means all you have to test on the GUI is that the right dialog boxes are displayed for each error condition, and that the correct page is displayed when the user is authenticated.

jfcUnit makes testing Swing GUIs quite easy. However, it is quite laborious for testing something that isn’t likely to break, like your logon box. Ordinarily, you’d save this type of testing for fully interactive forms where the steps are somewhat more involved.Tracking the Causes of Test Failures with JUnit Assertions
JUnit assertions all have the ability to take a string as a name that will be output with a test’s failure details. This makes tracking down the cause of a failure easier when using check methods. If to the parameter list for the dialog-checking method you used, you’d get the following method:

private void checkDialogAppears(String testName, String expected, 
String usernameText, String passwordText) throws Exception { setText(username, usernameText); setText(password, passwordText); helper.enterClickAndLeave(new MouseEventData(this, ok)); List showingDialogs = helper.getShowingDialogs(main); assertEquals(testName, 1, showingDialogs.size()); JDialog dialog = (JDialog) showingDialogs.get(0); assertEquals(testName, expected, dialog.getTitle()); helper.disposeWindow(dialog, this);}

The four routines that use this method are rather similar, except for the strings they use. So you could bring them together into an array of test data with a single test method, like this:

public void testOkayBehavior() throws Exception {  String[][] testData = {    {"Blank username", "Logon Error: username", "", "admin"},    {"Blank password", "Logon Error: password", "admin", ""},    {"Blank username & password", "Logon Error: username/password", "", ""},    {"Logging on", "Logging on", "admin", "admin"} };  for (int i = 0; i < testData.length; i++)    checkDialogAppears(testData[i][0], testData[i][1], testData[i][2], 
testData[i][3]);}

If you need another test of this type, you need only add another line to the test data array. This is a common pattern in testing, and you can easily generalize it to other general input forms. You write a method that fills the form and tests that the next form is the one you expect, and then you fill the form from an array containing test data.

New Tools for Your Bag of Tricks
In an age when any professional Java developer has to know how to properly unit test the code he or she writes, JUnit, JUnit-Addons, and jfcUnit should be at the top of your bag of tricks. Use these tools to make writing your tests easier and more effective. Rarely will you need to dig deeper into your bag for less common tools.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist