Trouble with Testing "ActiveRecord" objects

I’ve recently been experimenting with making some changes to our domain layer to help simplify our design and reduce the number of useless objects that were cropping up throughout our code base.  In our previous model we had “Manager” classes that, well, managed stuff.   Our managers closely followed the Repository pattern as defined in Domain Driven Design.

public class CustomerManager {
   ObjectManager objectManager = new ObjectManager();
   public SaveObjectResponse SaveCustomer(Customer customer) {
     return objectManager.Save(customer);
   }

   public bool DeleteCustomer(Customer customer) {
     return objectManager.Delete(customer);
   }

   public List<Customer> FindAllCustomers() {
     return objectManager.FindAll<Customer>();
   }
  
   // etc…
}

What this resulted in was a whole bunch of classes that looked a lot like the above.  I’ve used the ActiveRecord pattern a fair amount in the past so in an attempt to simplify our design we ended up adding some methods directly on top of our domain objects so that all of the above code disappeared.  To make them dissappear we added a base entity that defines a number of methods for saving, deleting, and finding domain objects.

public abstract class BaseEntity<T> {
  static IDatabase<T> db = new Database<T>();
  public SaveObjectRespnse Save() {
    db.Save(this);
  }
  public bool Delete() {
    db.Delete(this); 
  }
  public static List<T> FindAll() {
    db.FindAll(); 
  }
}

public class Customer : BaseEntity<Customer> {}

While this cleaned up our code and made it so we could get rid of a number of objects it has caused one issue.  We now have statics, which means we have a testability issue.  In order for us to enable things to be tested we needed a way to inject a IDatabase<T> into our Customer class.  Since our base entity has static methods that require the database it requires our database instance to be static.  This prevents us from using dependency injection to inject our mock database for testing.  As a short term solution I added a means for registering and unregistering a database like so:

public static void RegisterDatabase(IDatabase<T> dbToRegister) {
  db = dbToRegister;
}

public static void UnRegisterDatabase(IDatabase<T> dbToUnregister) {
  if(db == dbToUnregister) {
    db = defaultDb;
  }
}

While this works its far from ideal. 

[Test]
public void DoSomethingThatDoesNotNeedTheRealDatabase() {
  MockDatabase<Customer> mockCustomerDb = new MockDatabase<Customer>();
  mockCustomerDb.FindAllResult = myDummyCustomerList;
  Customer.RegisterDatabase(mockCustomerDb);
  try {
     // do some stuff that calls Customer.FindAll();
  }
  finally {
    Customer.UnRegisterDatabase(mockCustomerDb);
  }
}

As a result of having some ugliness due to the use of statics I think we’ll likely move to having a “Manager” like base class that supports generics.  This will have the same advantages of the current implementation and cause less issues when it comes to testing. 

public class Repository<T> {
  public SaveObjectResponse Save(T item) {…}
  public bool Delete(T item) {…}
  public List<T> FindAll() {…}
}

I wonder how the folks who are using Castle’s ActiveRecord handle this issue.  From browsing through their code base it doesn’t appear that they make it so you can mock out the ActiveRecord’s behavior easily which typically results in a lot more tests hitting the database then necessary.

# re: Trouble with Testing "ActiveRecord" objects

Friday, April 14, 2006 4:54 AM by Ayende Rahien    
In Castle's ActiveRecord, you can register a scope, and the scope will give you a session.
This is one way to do this.

I really like the generic repository angle, and I tend to use it a lot.

I posted about it here:
http://www.ayende.com/Blog/2006/04/14/TestingActiveRecordObjects.aspx

# re: Trouble with Testing "ActiveRecord" objects

Friday, April 14, 2006 9:33 AM by Steve    
Thanks Ayende, I'll check out how the scoping works in ActiveRecord/HNibernate. I'm thinking that the generic repository will end up being cleaner in the end but I do like having the methods available on my objects as well so we'll have to see what ends up working the best.

# re: Trouble with Testing "ActiveRecord" objects

Monday, April 17, 2006 10:36 AM by Sam Gentile    
Manual trackback

# re: Trouble with Testing "ActiveRecord" objects

Monday, April 17, 2006 11:34 AM by Murat    
In general I don't like Active Record pattern since I think it violates single responsibility principle. And now you need to inherit all your domain classes from the BaseEntity which also limits flexibility. One other thing is in the scenario of saving multiples of the same object type, the "manager" class would be instantiated only once where as the Database class in the BaseEntity class would be instantiated as many times as the number of domain objects.

However I aggree that it simplifies design a little bit. I also look the best practice to handle data access. What about creating dto s and having domain object either inherit from them or composite them? Any thoughts?

# re: Trouble with Testing "ActiveRecord" objects

Tuesday, April 18, 2006 11:50 PM by no_mock    
why in the world you need mock? db is slow, but you need to test db, don't you? It is not necessary to make it so fine grained. testing facade-and-below is good enough. Extensive using mock is kid-playing -- bad image of TDD. or perhaps TDD is indeed just a half truth. We need another name, perhaps NKPT not kids playing testing.

# re: Trouble with Testing "ActiveRecord" objects

Wednesday, April 19, 2006 12:03 AM by Steve    
Murat, In the example you provided the Database class would only be instantiated once as well becuase it is a static on the BaseEntity.

While I agree that the ActiveRecord pattern does blur the "responsibility" line I think we need to keep in mind that design principles such as SRP are meant to guide us. We have other design principles that lead us to make our design cohesive which the ActiveRecord does a good job of. It's all a matter of tradeoffs and personal preference.

By the way I'm constantly refining, altering, and challenging my own personal preferences.

# re: Trouble with Testing "ActiveRecord" objects

Wednesday, April 19, 2006 12:11 AM by Steve    
There is a small subset of tests that should hit the database. These include any logic that involves complex queries or where clauses that your unsure of and or complicated save operations involving things that make you uncertain of yourself.

The remaining logic within your application most likely doesn't need to hit the database for testing purposes. The more tests that hit the database the slower the test suite is to run. Personally I want my tests to run as fast as possible and to hit the database the least number of times as possible.

That certainly isn't to say tests should never hit the database, just that it should be the exception not the rule.

Post a Comment

 
 
Prove you're not a spammer: 
0 + 1 =