Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

A Cleaner MVC Inspired by Continuation-Passing Style

06.22.2011
| 19264 views |
  • submit to reddit

7. Testing

It's worth while having a look at how tests are implemented. Below there's an excerpt of the test for DefaultNewsViewController, the default implementation of NewsViewController:

public class DefaultNewsViewControllerTest 
  {
    
    private DefaultNewsViewController fixture;
    
    private NewsView view;
    
    private NewsService newsService;
    
    private NewsService.DownloadConfirmation downloadConfirmation;
    
    private FlowController flowController;
    
    @BeforeMethod
    public void setupFixture() 
      {
        view = mock(NewsView.class);
        flowController = mock(FlowController.class);
        newsService = mock(NewsService.class);
        downloadConfirmation = mock(NewsService.DownloadConfirmation.class);
        fixture = new DefaultNewsViewController(view, flowController);
      }
    
    @Test(timeOut=2000)
    public void showNewsFeed_must_start_downloading_when_the_news_feed_can_be_downloaded_and_the_user_confirms()
      throws Exception
      {
        doAnswer(notifyFeedCouldBeDownloaded()).when(newsService).getNewsFeed(any(AvailabilityNotifier.class));
        doAnswer(confirm()).when(view).confirmToDownloadNews(any(UserNotificationWithFeedback.class));

        fixture.showNewsFeed();
        waitForViewInteraction();
        
        inOrder.verify(view).lock(argThat(notification("", "Loading news...")));        
        inOrder.verify(newsService).getNewsFeed(any(AvailabilityNotifier.class));
        inOrder.verify(view).confirmToDownloadNews(argThat(notificationWithFeedback("Confirmation", "Please confirm that you want to download the latest news.")));
        inOrder.verify(downloadConfirmation).confirmDownload(); 
        verifyNoMoreInteractions(view, flowController, newsService, downloadConfirmation);
      }
    
    @Test(timeOut=2000)
    public void showNewsFeed_must_dismiss_the_view_when_the_news_feed_can_be_downloaded_and_the_user_cancels()
      throws Exception
      {
        doAnswer(notifyFeedCouldBeDownloaded()).when(newsService).getNewsFeed(any(AvailabilityNotifier.class));
        doAnswer(cancel()).when(view).confirmToDownloadNews(any(UserNotificationWithFeedback.class));

        fixture.showNewsFeed();
        waitForViewInteraction();
        
        inOrder.verify(view).lock(argThat(notification("", "Loading news...")));        
        inOrder.verify(newsService).getNewsFeed(any(AvailabilityNotifier.class));
        inOrder.verify(view).confirmToDownloadNews(argThat(notificationWithFeedback("Confirmation", "Please confirm that you want to download the latest news.")));
        inOrder.verify(view).unlock();
        inOrder.verify(flowController).finish();
        verifyNoMoreInteractions(view, flowController, newsService, downloadConfirmation);
      }
  }

FlowController has not been described in this article (will be introduced in future), in any case just keep in mind that it's a navigation controller and its finish() method means that the current view must be dismissed (whatever it might mean with the specific UI technology used for the implementation).

As a general note, this design splits the behaviour implementation from a single class into multiple smaller classes. This is good, but the inconvenience is that to set up a unit test you have to prepare a few more mocks: for instance, mocking NewService is not enough as DownloadConfirmation is needed too. But usually it's not a problem.

The test uses TestNG, Mockito and a few Mockito custom classes for providing some syntactic sugar; in particular, notifyFeedCouldBeDownloaded(), confirm() and cancel() are implementations of Mockito's Answer to mock the behaviour of callbacks. As an example. here's the listing of one of these Answers:

    @Nonnull
    private Answer<Void> notifyFeedCouldBeDownloaded()
      {
        return new Answer<Void>()
          {
            public Void answer (final @Nonnull InvocationOnMock invocation) 
              {
                final AvailabilityNotifier notifier = (AvailabilityNotifier)invocation.getArguments()[0];
                notifier.notifyFeedCouldBeDownloaded(downloadConfirmation);
                return null;
              }
          };
      }    

As you can see, the tests are very readable and perfectly describe their associated scenarios. Furthermore, the fact that our classes have no dependencies on a UI technology makes it easier the task of writing (and running) tests without setting up a complicated context.

Another article will give more details on the view implementation.

AttachmentSize
it.tidalwave.bluebill.mobile.news_.png32.91 KB
it.tidalwave.bluebill.mobile.news_.ui_.png13.07 KB
package.png12.46 KB
UserNotificationWithFeedback.png21.21 KB
Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)