Skip to main content

Effective JavaFX Architecture Part 2 - Test Driven Development (TDD) with JavaFX

Posted by srikanth on July 7, 2010 at 5:50 AM PDT

In the last installment of my post, I briefly described how to do Test Driven Development (TDD) in JavaFX using Model-View-Presenter (MVP) pattern. In this installment, I illustrate this particular piece in detail and provide working code samples. You can download the code here (Caution: 6.7 MB download).

The download is a zip file containing all 4 Netbeans projects. The code is tested with libraries compatible with JavaFX 1.3 and with Netbeans 6.9. You will also need a Glassfish v3 instance to deploy the web application. No additional configuration is needed. The application uses the out of the box data sources configured in Glassfish v3 (jdbc/sample) to connect to the built-in Derby database schema "SAMPLE"

Test Terminology

I have used JUnit and jMock to build Unit Tests in the samples. If you are not familiar with jMock, or have a faint idea about the difference between fake objects, Test Stubs, mock objects etc., look at the brief definitions of the test terminology, [in line with their definitions from the xUnit Patterns book - Great book, by the way]

  1. System Under Test (SUT) or Object Under Test: This refers to the actual object or combination of objects being tested
  2. Collaborator: A SUT does not operate in isolation. It relies on other objects for input and depends on some other objects for intermediate input/output. This set of objects that SUT depends are called collaborators
  3. Fake Object: A much light weight implementation of a component that SUT depends on
  4. Test Stub: Test stubs provide input to SUT. When I use test stub, it is almost always auto generated. By auto generated, I mean that I use jMock to mock the object and set the input expectations on it. The auto generated test stub provides mock input to the SUT. As you will see later, there is no need for a stubbed UI in JavaFX.
  5. Mock Object: Mock object is like an auto generated stub, except that it provides output that SUT relies on. Hence the expectations set into the mock object test the SUT behavior

Mapping Model-View-Presenter components to TDD

Figure 1 below shows these objects as color coded entities in a traditional TDD based on Model-View-Presenter.

Traditional TDD

Figure 1. Traditional TDD for UI

A few salient points about Figure 1:

  1. The UI Presenter is the SUT.
  2. The stubbed UI provides the input data. Traditional TDD for UI relies on stubbing the UI. This is because the UI creation itself is difficult. The UI implements an interface and the widgets are identified as Clickable, Selectable etc.
  3. All other Collaborators are faked or mocked.
  4. The UI Presenter has reference to the UI Interface, presentation model and the service interface (one or more).
  5. The UI also holds a reference to the Presenter

JavaFX variant of TDD

In JavaFX, I treat the UI (Node) and the Presenter as a single unit (SUT Couplet) as shown in Figure 2. The JavaFX specific nuances that result in a slightly different form of test setup are as follows:

  1. In JavaFX it is cumbersome to declare the UI interface and then create the stub and actual UI class.
  2. Declaring the UI using an interface makes it more Java-ish, thus losing advantages of the JavaFX style including binding – which is crucial for UI states, enabling, disabling etc.

Figure 2. JavaFX TDD for UI

A few salient points about JavaFX based TDD:

  1. There is no stub for the UI. The UI is created AS IS with a bunch of fields
  2. The only logic that resides in JavaFX UI is the state binding to the model data. The second non-logic piece that resides in UI is the layout.  The initial focus during TDD is to get the former correct.
  3. The layout with HBox and VBox can be created after the initial functionality is completed. Initially, the UI contains a create() method returning a javafx.scene.Group with all the UI widgets as its contents.
  4. The presenter is not the sole SUT. The presenter and the Node together form the SUT couplet.

Sample Application

Now let us turn our attention to the actual application I created to demonstrate the TDD with MVP and a few other architectural aspects as well. The sample application has 2 screens. Figure 3 shows these 2 screens.

  1. The first screen allows the user to simulate login as an end user or as store manager.
  2. The subsequent screen allows the user to search for products.
  3. The end user can only search on product code. Store Manager can search on product code and quantity on hand.
  4. The end user sees 4 product fields – Code, Description, Sales Price and whether available or not.
  5. The store manager sees 5 product fields – Code, Description, Purchase Price, Sales prices and Quantity on Hand

 

Figure 3. Screen 1 and Screen 2 of the Sample Application

Application Architecture

The application uses simplified 3-tier architecture as shown in Figure 4 below. I will describe the architecture using the product search use case.

  1. The JavaFX client side consists of ProductSearchNodePresenter and ProductSearchService Hessian Proxy wired together by Spring. (In fact all components are wired using Spring.)
  2. When  the user clicks on Search, the UI just calls presenter.doSearch(). You will notice that the node falls back to the Presenter for everything - for decision making during binding, for event handling and making backend calls and getting data and setting into the Node back again or traversing to the another node – the whole shebang.
  3. The Presenter uses the Product Search Service (already injected by Spring) to communicate with the remote services
  4. The backend/remote services is a web container with a simple Hessian servlet implementing the Service interface. It receives the call and responds with data.
  5. All data exchange between the two ends is via Hessian over HTTP.
  6. The presenter creates appropriate Presentation Model objects and sets the data into the table/grid

Sample JavaFX Application Architecture

Figure 4. Application Architecture for the Sample JavaFX Application

Spring based Injection in JavaFX

The sample JavaFX application is entirely wired using Spring. This is no accident. The benefits of using Spring in any decent sized application - whether it is JavaFX, Swing or on the server side goes without saying. Spring Dependency injection, when used correctly, adds value to the architecture and simplifying it. Okay, Enough said.

The spring configuration xml for the JavaFX application defines the Node Presenter, Search Service Hessian Proxy and are appropriately injected into one another as required. For instance, ProductSearchNodePresenter is injected with the a Spring Hessian Proxy implementing the IProductSearchService interface.

<beans ...>
    <bean id="productSearchService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
        <property name="serviceUrl">
            <value>http://${serverName}:${port}/${contextPath}/ProductSearchServlet</value>
        </property>
        <property name="serviceInterface">
            <value>org.fxobjects.mvp.service.productsearch.IProductSearchService</value>
        </property>
    </bean>
    <bean id="productSearchPresenterBean" class="org.fxobjects.mvp.ui.productsearch.ProductSearchNodePresenter">
        <property name="id" value="ProductSearchNodePresenter"/>
        <property name="productSearchService" ref="productSearchService"/>
    </bean>
...
..
</beans>

The spring xml is loaded in the Main  script as follows:

var ctx = new ClassPathXmlApplicationContext("org/fxobjects/mvp/mvp-spring-beans.xml");
var appController:ApplicationController = ctx.getBean("appController") as ApplicationController;

Note: If you have looked at the code already, you also get an idea of how the so called Application Controller gets the right node and binds it to the scene graph. In the xml, you just mark the desired node presenter as default node presenter and the Application Controller picks that and displays it as first screen. This is a simplified implementation of the so-called controller chain – which I will discuss in future posts.

Netbeans Projects

3 Netbeans projects are created as shown below in Figure 5. One additional project holds the JUnit Tests. Both the front end and the backend projects depend on mvp-common – a Java class library project that holds the classes for data exchanged and also the service interfaces.

Figure 5. Netbeans projects in the Sample JavaFX application and their inter-relationships

Note that the JUnit project is nothing but a JavaFX script project with a script file with run() method. This run() is marked as the entry point. All Tests are run from here. At this point Netbeans does not provide a mechanism to individually select a TestCase and run it. Hence it all has to be run from this run() function. This JavaFX-JUnit project imports all necessary jars for testing (JUnit, JMock) and spring, mvp-javafx and mvp-cmmon.

public function run():Void {
    var runner:TestRunner = new TestRunner();

    //Verify that the first node displayed is the LoginNode.  
    runner.run(DefaultNodeTest.class);
  
    //LoginNodePresenter and LoginNode Unit Test
    runner.run(LoginNodeTest.class);

   //ProductSearchNodePresenter and ProductSearchNode unit tests
    runner.run(ProductSearchNodeTest.class);
}

Getting to TDD

Ok. Now I get to the core topic of this blog post. How to do TDD with JavaFX correctly?  When I do TDD with JavaFX – here is how I proceed.

Preparations

  1. First and foremost: The interface and objects for data exchange are fleshed out to some degree
  2. Then, I just create the Node with the widgets, but without its layout. I just create a Group and add all widgets to it and return in create() function. The widgets do not have any bind logic. They are dumb widgets at this point.
  3. Then I create a Presenter JavaFX class and create bidirectional reference between the Presenter and the Node.
  4. I add event handling function in the Node (for instance button clicks) to just call functions in presenter
  5. Then I stub the callback method in the presenter.

Now the real TDD fun starts

Now, let me start writing JUnit tests for verifying the initial state of the UI. For e.g. the ProductSearchNode should not display the quantity on hand search box for end user. Since my application looks for the currently logged in user in a predefined place, I do the following in my JUnit test

public class ProductSearchNodeTest extends TestCase {
    var presenter:ProductSearchNodePresenter;
    var node:ProductSearchNode;

    public override function setUp():Void {
        presenter = ProductSearchNodePresenter { };
        node = presenter.getNode() as ProductSearchNode;
    }

    //test Quantity On hand textbox is invisible to end user
    public function testQOHInvisibleForEndUser():Void {
        ApplicationData.currentlyLoggedInUser = TestUtil.createDummyEndUser();
        assertFalse(node.quantityOnHandTextBox.visible);
    }

Obviously the test fails now, so I proceed with fixing it. The fixing involves adding the following bind condition (in bold) to the JavaFX UI Node (and the test will pass after that)

    public-read var quantityOnHandTextBox:TextBox = TextBox {
        visible: bind prodSearchNodePresenter.shouldShowQuantityOnHandOption
        layoutInfo: LayoutInfo { width: 50 }
    };

A few points to note here:

  1. The TextBox does not decide whether it should make itself visible. Instead it relies on a appropriately exposed bind variable in the Node Presenter
  2. Also note the Presenter is created in setUp() function in the testcase. And then the presenter creates the actual Node.
  3. Since Presenter is in charge, it should always create the Node. This puts the Presenter in control to decide whether the Node should be destroyed or to keep it around for future display. The Node itself should not make that decision. In this sample application, presenter.getNode() function caches the Node and returns the same instance for subsequent invocations.
  4. Since it is the presenter that creates the Node and also gets callback when button clicks occur, we can easily simulate button clicks by calling the appropriate callback methods on the presenter directly from the test. Not even a stubbed out event is required! Can’t get easier than that.

More TDD and what a Mockery!

Now let us look at another JUnit Test. The Quantity On Hand is a textbox in the Search UI and the data entered should  be numeric. But right now, the text box allows anything. (Well, I could have used a TextBox that allowed only numeric characters, but then I could not have possibly demonstrated this jMock introduction test case to you !).  Here is what I envision as happening when the doSearch is clicked to trap those data type errors

  1. When non numeric data is entered into the quantity on hand textbox and search button is clicked, the Presenter.doSearch() is called.
  2. The doSearch method validates the data types and then create a list of error messages
  3. Then it passes the error messages to the Application Controller for display.
  4. No further functionality is invoked.

Note that our previous test involved pure state checking. That was achieved with assertXYZ() calls. Our current test involves checking the internal behavior of the doSearch() function itself. How do we test that? That’s when jMock comes into picture.

  1. jMock allows me to mock interfaces.
  2. This in turn allows me to plug the mock service implementations into the Presenter instead of the real implementations.
  3. Also, as you might have seen in the code, the NodePresenter is owned by the ApplicationController – a top level class that controls the entire application. ApplicationController implements the IApplicationController interface and has many methods, and one of them useful for us right now is displayErrors(). This method can be called to display the errors.

Based on this, here is how my test code looks like

  • Create a Mockery object. This acts as the context for jMock expectations.
var jMockContext:Mockery = new Mockery();
  • Set a non numeric text value in the Quantity On Hand textbox
    public function testSearchDataTypeError():Void {
        ApplicationData.currentlyLoggedInUser =
                              TestUtil.createDummyStoreManager();
        node.productCodeTextBox.text = "SW";
        node.quantityOnHandTextBox.text = "ABCDEF";
       .. ..

    }
  • Create a mock implementation for the IApplicationController and set it on the Presenter.
        var mockAppController:IApplicationController = 
                      jMockContext.mock(IApplicationController.class);
        presenter.setOwningUnit(mockAppController);
  • Set the expectations. Expectations let me sketch my expected behavior of SUT when interacting with a mock object – expected method call, how many times, expected input, expected output and in what sequence etc. This one below sets the expectation that the displayErrors() function on mock controller is called with a list of errors.
        var expectedErrorMsgList = new ArrayList();
        expectedErrorMsgList.add("Some error message");
        var expectation:Expectations = new Expectations();
        expectation.oneOf (mockAppController).
                          displayErrors(expectedErrorMsgList);
        jMockContext.checking(expectation);
  • Make the actual doSearch() call on presenter.
        presenter.doSearch();
  • If the doSearch() behaved as expected, then it should internally create the data type errors list and call the ApplicationController.displayError(List). This verification is done by caling the following at the end. An exception is thrown if the actual behavior did not match the expected behavior.
        jMockContext.assertIsSatisfied();

NOTE:

  1. I ate my own dog food when writing this test code.  In this test I wanted to peek into the internal expected behavior of the SUT without mocking the SUT itself!!
  2. Initially I was planning to code the entire logic of validation and error display within doSearch(). With that approach, I would not be able to test the error behavior.  Hence I added the displayErrors(List) into ApplicationController.
  3. But ApplicationController was a JavaFX class. An interface would have been better. Hence I ended up extracting the IApplicationController interface from the existing ApplicationController by refactoring.
  4. Similarly the parent class NodePresenter was using the ApplicationController straight away. I ended up changing this to use the IApplicationController interface.
  5. This allowed me to mock the application controller which is a great thing because it allowed me to plugin mock app controllers and check expected behavior, navigation behavior among other things.
  6. As they say, TDD helps to refactor code to create cleaner and elegant code. This is one such good example to prove that point.

More mockery and Expectations – JavaFX doesn’t like the double braces

We aren’t done with mocking yet. I now show you the test code for an actual search by mocking the IProductSearchService (and that JavaFX does not allow the double brace syntax of jMock Expectations and how to get around it).
To test a normal search that returns results, I setup the test code as follows. I believe you are comfortable with most of the code except the code in bold

    //test that a normal search returning result from server will display all the data fetched in UI
    public function testSearchAll():Void {
        ApplicationData.currentlyLoggedInUser = TestUtil.createDummyStoreManager();
        node.productCodeTextBox.text = "";
        node.quantityOnHandTextBox.text = "";

        var crit = new ProductSearchCriteria();
        crit.setProductCode("");

        var resultList:List = new ArrayList();
        var p1:Product = new Product("SW", "Software", 100, 8.00, 100);
        var p2:Product = new Product("VW", "Volkswagen", 100000, 10000.00, 100);
        resultList.add(p1);
        resultList.add(p2);

        //create the mock service and set it as the real service
        var mockSearchService:IProductSearchService =
                         jMockContext.mock(IProductSearchService.class);
        presenter.setProductSearchService(mockSearchService);

        var mockAppController:IApplicationController =
                         jMockContext.mock(IApplicationController.class);
        presenter.setOwningUnit(mockAppController);

        /*
            set expectations that Search Service will be called with a empty search criteria
            after that assert the state that the results are a non zero and set in table and the presentation model
        */
        var expectation:Expectations = new Expectations();
        expectation.oneOf (mockSearchService).searchForProducts(crit);
        expectation.will(
            expectation.returnValue(resultList)
        );
        jMockContext.checking(expectation);

        presenter.doSearch();

        jMockContext.assertIsSatisfied();
        assertTrue(sizeof presenter.products == 2);
        assertTrue(sizeof node.productTable.rows == 2);
    }

The code in bold also shows the expected return value from the search service. Notice that it is a bit verbose. A similar jMock expectation in Java would have looked like this:

Expectations expectation = new Expectations() {
    {
        oneOf (mockSearchService).searchForProducts(crit);
        will(returnValue(resultList);
    }
});

Now I call this is a ideal compact and fluent interface. However JavaFX does not like the double braces for instance initialization blocks. Hence we have to break it down into non-fluent method calls.

Final dose of jMock – Using named sequences for in sequence verification and failure verification

Here is my final dose of jMock to get you test infected ?. This one tests the behavior of the SUT when the remote service is unreachable. The code in bold shows the sequencing of expected behavior when more than one “thing” is expected.

    //test that a failure in the remote service (manifested as a spring framework remote connect failure exception) is dealt correctly by the UI and is dispatched to
    // Application controller to display the errors
    public function testSearchServiceFailure():Void {
        ApplicationData.currentlyLoggedInUser = TestUtil.createDummyStoreManager();
        node.productCodeTextBox.text = "SW";
        node.quantityOnHandTextBox.text = "";

        var innerDummyException:Exception = new Exception();
        var thrownException:RemoteConnectFailureException =
           new RemoteConnectFailureException("Cannot connect to service",
                                                       innerDummyException);

        var crit = new ProductSearchCriteria();
        crit.setProductCode("SW");

        //create the mock service and set it as the real service
        var mockSearchService:IProductSearchService =
                    jMockContext.mock(IProductSearchService.class);
        presenter.setProductSearchService(mockSearchService);

        var mockAppController:IApplicationController =
                    jMockContext.mock(IApplicationController.class);
        presenter.setOwningUnit(mockAppController);

        /*
            set expectations that when there is a failure on any kind of searching,
            the applicationController's handleError is called
            after that assert the state that the results are empty in table and the presentation model
        */
        def seqName:String = "service exception sequence";
        def exceptionSeq:Sequence = jMockContext.sequence(seqName);
        var expectation:Expectations = new Expectations();
        expectation.oneOf (mockSearchService).searchForProducts(crit);
        expectation.will(
            expectation.throwException(thrownException)
        );
        expectation.inSequence(exceptionSeq);

        //assert that the handle Error on the controller is called
        expectation.oneOf(mockAppController).
                    handleError(thrownException,
                       ProductSearchNodePresenter.SEARCH_SERVICE_ERROR_MSG);
        expectation.inSequence(exceptionSeq);

        jMockContext.checking(expectation);

        presenter.doSearch();

        jMockContext.assertIsSatisfied();

        assertTrue(sizeof presenter.products == 0);
        assertTrue(sizeof node.productTable.rows == 0);
    }

The sequence name in the above listing (code in bold) ties together all the expected behavior in that order and sets the expectation that they are invoked in that order. The Mockery.assertIsSatisfied() verifies the order of invocation as well.

Summary

This was a long blog indeed. I am glad that you are still awake and made to this point !! But hopefully I have kept this very important concept of TDD readable and conveyed the importance of doing TDD in making your system architecture elegant and building a system that works today and works tomorrow. I have also demonstrated that MVP (Passive View MVP) is your friend in getting an elegant TDD friendly JavaFX architecture. Along the way you have also seen how I used Spring for loosely coupling the system with dependency injection and all that jazz. You also got a sneak peek at the Controller Chain and how it is used for node navigation. I encourage you to download the accompanying application and go through it to understand the code in detail (link here). And don’t forget to write elegant TDD style JavaFX applications

PS: As a parting note, I would also like to note that JFxtras – a fine collection of JavaFX controls has its own Testing framework. It is based on JUnit, but extends it in some ways to write JavaFX-ish tests. It also has an Expectations framework. However I am not sure if it allows me to mock interfaces, set expectations, sequences etc. in a way that jMock does. I was familiar with jMock from my previous projects. Hence I used JUnit and jMock in this post. If you are adventurous, try it out and let me know ?

What’s next

This is not the end of it !!. There is more on the way. Notice how I used synchronous communication for connecting to remote services. What I should have really used is asynchronous communication. But that would muddle the concepts and make things complex. In the next installment I will cover asynchronous communication with the remote services using asynchronous Command objects and how to test them. Following that, I will show how to break a large JavaFX application into modules, using Application Controller, Module Controller and using Application Events. Another future blog will cover mechanisms for data exchange – not specific to JavaFX but in general those that play well with overall “agility” of the architecture. Of course Security and dealing with it is also in the offing. So, watch out this space for more blog posts related to Enterprise JavaFX architecture – the effective way! And do not forget to provide feedback and what would you like to see covered in this exciting area.

Comments

&nbsp;Link to sample code is dead!&nbsp; Please let us ...

Link to sample code is dead!

Please let us know, if it still exists elsewhere if possible.

Thanks!