Jeff's Blog

Musings about software development, Java, OO, agile, life, whatever.


Tuesday, November 01, 2005 
Database TDD Part 14: More Complex Mocking
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<String> 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 and getKeyColumn. 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<String> 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<String> 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<String> 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<String> 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<String> 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, and expectedRow. 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.


Comments: Post a Comment

Links to this post:

Create a Link



<< Home

RSS Feed (XML)

Archives

February 2004   March 2004   May 2004   September 2004   October 2004   January 2005   February 2005   September 2005   October 2005   November 2005   December 2005   January 2006   February 2006   March 2006   June 2006   August 2006   January 2007   February 2007   March 2007   April 2007   September 2007   October 2007   November 2007   December 2007   January 2008  

This page is powered by Blogger. Isn't yours?