• Home
  • Events
  • About
  • Advanced Mocking: Capturing State with Answer and Captors

    Hmmm… I really need to mock out java.sql.ResultSet, I know I’ll just subclass it with a hand-rolled mock. Oh-no, the default implementation without method bodies is 650 lines of code long. WTF? Did you know there are 197 public methods on the java.sql.ResultSet interface? This is an absurdly large interface, possibly the largest in the JDK (if not drop a comment, I’d love to know what the largest is). The workaround is to throw one together in Mockito (or your mock framework of choice). My implementation turned out to be 21 lines of code long. Not bad at all. To do it I creatively used ArgumentCaptors, Answer objects, and anonymous inner classes to capture state. The samples below are in Java, but they would be a little nicer in Groovy.

    Anyway, here is the API I wanted to end up with… specify a String List for the column names, and then give any number of Object Lists for the data.

    As you can see, the makeResultSet method creates a ResultSet object. ResultSet is part Map implementation and part iterator. As a Map, you can ask things like getString(“columnName”) and get a String result for a named column. Tons of data types are supported. As an Iterator, you can call next() to advance the recordset cursor, which tells you if there is a next record and also allows you to access the next record’s contents. It’s a pretty stateful object.

    So here is how I implemented it in Mockito. We’ll dissect it below.

    The basic flow is, create the mock ResultSet, mock out the iteration/cursor functionality, then mock out the row/column lookup functionality. Let’s take it from top to bottom.

    1. static – I make private methods static. Almost always. It makes refactoring and moving the method so simple. Plus, you can easily analyze the dependencies of the method. They are all right there in the parameter list, no syntax highlighter needed. Moving on…

    2. varargs – In unit test helper methods I use varargs a lot. It makes a really nice API for creating data. And remember, varargs is just syntactic sugar for a Java Array. In this example, rows is an Array of Lists. In production code you’d always want to check this parameter for null.

    3. final – The parameters are final because we’ll need to reference them from within anonymous inner classes.

    4. AtomicInteger – The result set functions as a cursor so there is always a current record. We model this as an integer and the current record is just the index into the rows array. It’s Atomic and final so that we can reference and modify it from within the anonymous inner classes.

    5. next() and Answer – The Mockito Answer interface is a high powered “thenReturn” statement. With “thenReturn” you can only return simple objects, with Answer you get to do the same with an anonymous inner class. Think of it as a “thenReturn” factory. Notice, next() returns false when the currentIndex advances beyond the end of the rows Array. And next() calls increment the index to advance the record pointer. When I say “capturing state”, the AtomicInteger is the state and it is captured within the Answer instance. It’s good to know the Atomic family of classes.

    6. Captore and Answer for Row/Column Lookup – Now we combine an ArgumentCaptor and another Answer object to implement the getString/getInt/getX functionality. The ArgumentCaptor is an object used to capture parameters sent to methods. It can tell us whether “Birthday” or “Name” was passed to our method. It wraps around the parameter and allows you to call it back later. Again, we use an Answer, combined this time with the Captor to return a row/column value. Our rowIndex is just the currentIndex pointer, and the columnIndex is the index of the parameter name (“Birthday” or “Name”) within the column list. From here it is just a multidimensional “array” access to get our cell value. Woot.

    7. when() and the rowLookupAnswer – the rowLookupAnswer can be reused for any datatype in the ResultSet. This isn’t a complete list but was everything I needed.

    The Point?
    Mock object frameworks are very much about creating objects without the usual constructors and concrete implementations. For my project, Mockito was insanely useful without even using the assertions and verifications that normally go with mocks. Maybe the code is a little complex, I admit, but at least it is short. And as long as you understand Captors and Answers then it really shouldn’t be a problem.

    Happy Mocking!

    Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
    • email
    • Print
    • Twitter
    • LinkedIn
    • XING
    • Facebook
    • Google Bookmarks

    11 Comments »

    1. josefB said,

      November 5, 2010 @ 23:12

      Now that is a thing of beauty! Just discovered Mockit and it is great.

    2. Peter Niederwieser said,

      November 5, 2010 @ 23:58

      Too bad you couldn’t use Groovy. 🙂 Have a look at (or run!) the Spock version of this code in Spock Web Console:

      http://meet.spockframework.org/?id=21001

    3. Peter Niederwieser said,

      November 5, 2010 @ 23:58

      Too bad you couldn\’t use Groovy. 🙂 Have a look at (or run!) the Spock version of this code in Spock Web Console:

      http://meet.spockframework.org/?id=21001

    4. Sam Beran said,

      November 6, 2010 @ 22:41

      Great blog as always Hamlet, I love Mockito.

      Here are the top 20 interfaces with the most methods in Java: (as generated by this ruby script: https://gist.github.com/665726 )

      1. javax.sql.rowset.JoinRowSet – 377 methods
      2. javax.sql.rowset.FilteredRowSet – 363 methods
      3. javax.sql.rowset.WebRowSet – 361 methods
      4. javax.sql.rowset.CachedRowSet – 355 methods
      5. javax.sql.rowset.JdbcRowSet – 322 methods
      6. javax.sql.rowset.spi.SyncResolver – 311 methods
      7. javax.sql.RowSet – 304 methods
      8. java.sql.CallableStatement – 208 methods
      9. java.sql.ResultSet – 189 methods
      10. java.sql.DatabaseMetaData – 174 methods
      11. javax.xml.soap.SOAPFault – 119 methods
      12. javax.xml.soap.SOAPBody – 104 methods
      13. javax.xml.soap.SOAPHeader – 104 methods
      14. javax.xml.soap.SOAPHeaderElement – 101 methods
      15. javax.xml.soap.SOAPEnvelope – 99 methods
      16. java.sql.PreparedStatement – 97 methods
      17. javax.xml.soap.Detail – 96 methods
      18. javax.xml.soap.DetailEntry – 93 methods
      19. javax.xml.soap.SOAPBodyElement – 93 methods
      20. javax.xml.soap.SOAPElement – 93 methods

      P.S. We’ll miss you at the Summit this year!

    5. Daily del.icio.us for November 4th through November 6th — Vinny Carpenter's blog said,

      November 6, 2010 @ 23:02

      […] Rich Internet Applications (RIA) » Blog Archive » Advanced Mocking: Capturing State with… – For my project, Mockito was insanely useful without even using the assertions and verifications that normally go with mocks. […]

    6. Hamlet said,

      November 7, 2010 @ 16:14

      @Sam What summit is that?

    7. Sam Beran said,

      November 7, 2010 @ 19:08

      The next Pearson tech summit is coming up at the end of the month – we met briefly last year and your named seemed familiar. I later realized that I was subscribed to your blog!

    8. anehra63 said,

      November 9, 2010 @ 11:39

      A very good article.

    9. roger said,

      August 8, 2013 @ 23:18

      Why did you need #6 to capture the arguments here? You could do just

      Mockito.when(result.getString(Mockito.anyInt())).thenAnswer(rowLookupAnswer);

      instead, right? Or what functionality does the captor do here?

    10. Hamlet said,

      August 9, 2013 @ 9:14

      I think you are right… in this example the Captor does not do much. I think I used it because I was doing some debugging that later got deleted. Typical!

    11. David said,

      January 25, 2014 @ 1:59

      Ah, I was thinking about something like this but didn’t know about the Answer class. Thanks!

    RSS feed for comments on this post · TrackBack URI

    Leave a Comment

    Time limit is exhausted. Please reload the CAPTCHA.

    css.php