Database TDD Part 14: More Complex Mocking

by Jeff Langr

November 01, 2005

Yesterday I extracted the common method save into the Persister class. The find can be dealt with almost as easily. The only trick is that there is a creation statement in it that builds an instance of the appropriate object (User or Customer). Before proceeding, then, step one is to extract this creation into a factory method. Step two is to update the PersistableMetadata interface so that it defines this creation method. (This suggests that the name for PersistableMetadata is no longer precise; we can deal with that later.)

After that, I move the findmethod to the Persister class. I then modify it to call back to the creation method defined on the access class. Here’s CustomerAccess:

    package domain;
    
    import java.util.*;
    
    public class CustomerAccess implements PersistableMetadata {
       public void save(Persistable persistable) {
          new Persister(this).save(persistable);
       }
    
       public Customer find(String idKey) {
          return (Customer)new Persister(this).find(idKey);
       }
    
       public String getTable() {
          return "cust";
       }
    
       public String[] getColumns() {
          return new String[] { Customer.ID, Customer.NAME };
       }
    
       public String getKeyColumn() {
          return Customer.ID;
       }
    
       public Object create(List row) {
          return new Customer(row.get(0), row.get(1));
       }
    }

Since the columns list and table name are now only used in one place, I chose to inline them to the interface methods getColumns andgetKeyColumn. These methods are now constant methods.The updated PersistableMetadata interface:

    package domain;
    
    import java.util.*;
    
    public interface PersistableMetadata {
       String getTable();
       String[] getColumns();
       String getKeyColumn();
       Object create(List row);
    }

The create method will have to return an Object, hence the cast in the CustomerAccess find method.

Persister:

  package domain;
  
  import java.util.*;
  
  import persistence.*;
  
  public class Persister {
     private PersistableMetadata metadata;
     private JdbcAccess access;
  
     public Persister(PersistableMetadata metadata) {
        this(metadata, new JdbcAccess());
     }
  
     public Persister(PersistableMetadata metadata, JdbcAccess access) {
        this.metadata = metadata;
        this.access = access;
     }
  
     public void save(Persistable persistable) {
        String[] columns = metadata.getColumns();
        String[] fields = new String[columns.length];
        for (int i = 0; i < columns.length; i++)
           fields[i] = persistable.get(columns[i]);
        String sql = new SqlGenerator().createInsert(metadata.getTable(), columns, fields);
        access.execute(sql);
     }
  
     public Object find(String key) {
        String sql =
         new SqlGenerator().createFindByKey(
          metadata.getTable(), metadata.getColumns(), metadata.getKeyColumn(), key);
        List row = access.executeQuery(sql);
        return metadata.create(row);
     }
  }

And finally, the missing test in PersistableTest:

    package domain;
    
    import java.util.*;
    
    import junit.framework.*;
    import persistence.*;
    
    public class PersisterTest extends TestCase {
       private static final String TABLE = "x";
       private static final String[] COLUMNS = { "a", "b" };
       private static final List EXPECTED_ROW = new ArrayList();
       private static final Object RETURN_OBJECT = "object";
    
       private JdbcAccess access;
       private String lastSql;
       private PersistableMetadata metadata;
    
       protected void setUp() {
          access = new JdbcAccess() {
             public void execute(String sql) {
                lastSql = sql;
             }
    
             public List executeQuery(String sql) {
                lastSql = sql;
                return EXPECTED_ROW;
             }
          };
    
          metadata = new PersistableMetadata() {
             public String getTable() {
                return TABLE;
             }
    
             public String[] getColumns() {
                return COLUMNS;
             }
    
             public String getKeyColumn() {
                return COLUMNS[0];
             }
    
             public Object create(List row) {
                if (row == EXPECTED_ROW)
                   return RETURN_OBJECT;
                return null;
             }
          };
       }
    
       public void testSave() {
          Persistable persistable = new Persistable() {
             public String get(String key) {
                if (key.equals(COLUMNS[0]))
                   return "0";
                if (key.equals(COLUMNS[1]))
                   return "1";
                return null;
             }
          };
    
          Persister persister = new Persister(metadata, access);
          persister.save(persistable);
    
          assertEquals("insert into x (a,b) values ('0','1')", lastSql);
       }
    
       public void testFindBy() {
          final String key = "k";
          Persister persister = new Persister(metadata, access);
          assertEquals(RETURN_OBJECT, persister.find(key));
          assertEquals("select a,b from x where a='k'", lastSql);
       }
    }

The method testFindBy was pretty easy to write, considering that I already had the Persistable, PersistableMetadata, and JdbcAccess test implementations/stubs. Here, I’ve added a stub for executeQuery and create. With refactoring to eliminate duplication in the test, the individual test methods are pretty succinct.

The Persister tests leave a slightly sour taste in my mouth, as do most tests where I introduce more complex mock interrelationships. Here, you have to dig around the test class a bit to understand the relationship between executeQuery, create, returnObject, andexpectedRow. I don’t have a problem with mocks per se, but they are best isolated to as few corners of your system as possible if they are at all tricky.

Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.