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.