ActiveRecord is hard to test

June 29th, 2009 · 1 Comment ·

Reader asks:

have a question regarding testing of Active Record style domain models

The problem is: client code of these objects will instantiate them directly, modify some attributes and then invoke a save(), or insert() method directly on the object. Writing a unit test that doesnt talk to the database is difficult since the save/insert methods are inherited from a super class which handles the persistence using an object responsible for database access.

As I see it, the problem is that Active Record stlye domains cross the boundary between what you desribed as Newable/Injectable. The “model” part is newable, but the data access part is injectable (ideally).

The challenge I’ve had testing is finding a suitable point at which to mock (or stub) the database access. I thought of replacing the new CustomerModel() (for example) with a call to a factory that would be provided using dependency injection, such as factory.createCustomerModel() but this feels like a bit of hack, and indeed you’ve warned against this directly in some of your posts.

The other option would be to define something static on the model super class to allow the default database object to be replaced before the test runs the client code. Again though, this doesnt “feel” that right to me.

What would you suggest?

I am assuming that you have read: http://misko.hevery.com/2009/05/05/the-problem-with-active-record/. The issue here is that the Record should be newable (http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/), and as such it should have no reference to service objects. In other words the save method needs to move some place else. A good place for it is to move to is a repository. I really like the Hibernate model as it makes the repository a “watcher” which watches your newable objects and than saves them automatically when the transaction is up.

Here is an example:

CarRepository carRepo = ... inject me...;
Car car = new Car();
car.setAbc(...);
carRepo.save(car);

No problems here. In your test just inject an InMemoryCarRepository. The cool thing is that most of your business code should only deal with car. Only very few places need to know about CarRepository. Here is something else which is cool.

CarRepository carRepo = ... inject me...;
Car car = carRepo.getById(123);

enter business logic...
car.setAbc(...);
exit business logic...

carRepo.commit();

Since CaRepository knows about all of the Cars it has given out calling carRepo.commit() automatically saves all of the cars. Since most of your app modifies the entities most of it does not even need to know about CarRepository. Only classes which need to create or delete a car need to have a reference to a CarRepository.

From testing point of view this is great since most of my unit-test will be testing code from “enter business logic…” until “exit business logic…”, which means most of my tests will not even deal with CarRepository but only have to deal with a Car.

Tags: Uncategorized

1 response so far ↓

  • Giorgio Sironi // Jun 29, 2009 at 1:28 pm

    Active Record pattern is simple but database dependent. The only clean solution is persistence ignorance (first step towards DDD) and makes you capable of testing business objects and their interaction without using a db. Problem is that the DataMapper (like Hibernate or Doctrine) become more and more complicated with time.

Leave a Comment