Repositories 101
I got this question in an email today, and I think that it would make a good post. I broke it into two parts:
Let us talk about my Repository<T>, it is a static class, so it can't be mocked, but it is fully testable, since it uses Windsor to get the implementation of the IRepository<T> interface, of which there are several. Now consider the following code:
public ICollection<Customer> GetPreferredCustomers()
{
return Repository<Customer>.FindAll(Where.Customer.IsPreferred = true);
}
Is it testable? Yes, I can replace the internal implementation of the Repository<T> with my own, in this case, a mock. The test would look something like:
[Test]
public void GetPreferredCustomerWillForwardQueryToRepository()
{
//already setup with a fresh mock that it will return
IRepository<Customer> customersRepository = IoC.Resolve<IRepository<Customer>>();
Expect.Call(customersRepository.FindAll(null,null))
.Constraints(Criteria.Contains("IsPreferred = True"), Is.Anything). Return(null);
mocks.ReplayAll();
service.GetPreferredCustomers();
mocks.VerifyAll();
}
As you can see, it is not hard, but neither is it trivial. And I am not testing that the query works. For this types of situations, I am using in memory database and test against known data. I feel that it gives me as much value, and it doesn't hinder the tests.
If you really like, you can do it by taking an interface like my IRepository<T> and use that, just stripping all the NHibernate specific parts. The reason I don't like it is that it is a false promise. You won't be able to easily switch to another ORM easily. Each ORM does it magic differently, and trying to move from NHibernate to LLBLGen (or vice versa), to make a simple example, is not going to be "let us just change the repository implementation" and that is it.
That would actually be the easy part, the far harder issue would be to make sure that logic that relies on the existance of Unit of Work (or auto wiring of reverse dependencies, to take something that NHibernate does not do), is much harder. You can limit yourself to simple CRUD operations, in which cases the differences are neglible in most scenarios, but that is missing out a lot, in my opinion.
Comments
These sort of posts are really useful. Thanks.
I agree, Please continue Ayende :)
Thank you sir!
Having a truly generic repository becomes a bit easier with LINQ. I implemented a version of your Repository<T> pattern on a recent project that used LINQ to SQL and was able to substitute an in-memory database no problems. Repository finders accept LINQ expressions that can be used to query either the database or in-memory collections.
That may be a great solution to this, yes.
I am concerned about the difference in implementation, but that puts a general query in the hand of the repository, and the burden of making it work is with the hands of the repository, not the app developer, which is as it should be.
thanks for make things clear !
Can you please sketch the ideea how to setup mocks for sample that you provide?
Sorry, i'm new to Rhino & Windsor, but i find these tools just great. I stuck some time with [SetUp] section of my test fixture, but still not able to wire together MockRepository, Repository<T>, WindsorContainer & IoC .
Thanks in advance.
And thank you for your posts. You provide a really good content. You do a really good job.
The code above makes a few assumptions, that you are using Windsor, that you are using Rhino Common's repository, etc.
Basically, the code in the setup is something like:
mockedContainer = mocks.DynamicMock<IWindsorContainer>();
customersRepository = mocks.DynamicMock<IRepository<Customer>>();
SetupResult.For(mockedContainer.Resolve<IRepository<Customer>>())
.Return( customersRepository ) ;
Ok, but as i understand, the next step should be initializing the IoC class with instance of mockedContainer:
IoC.Initialize(mockedContainer);
And after that, in our tests we should be able to retrieve from IoC our mocked instance of customerRepository:
IRepository<Customer> customersRepo = IoC.Resolve<IRepository<Customer>>();
But this step fails for me - instance is always null. I'm doing something wrong? Or i just have entered in "stupid mode" and i'm not seeing evident things? :)
Anyway, thank you for the responce...
Ok, but as i understand, the next step should be initializing the IoC class with instance of mockedContainer:
IoC.Initialize(mockedContainer);
And after that, in our tests we should be able to retrieve from IoC our mocked instance of customerRepository:
IRepository<Customer> customersRepo = IoC.Resolve<IRepository<Customer>>();
But this step fails for me - instance is always null. I'm doing something wrong? Or i just have entered in "stupid mode" and i'm not seeing evident things? :)
Anyway, thank you for the responce...
You haven't moved the mocked instance to replay mode, probably.
Comment preview