views:

337

answers:

1

I have been investigating how to decouple my DomainServices from their datasource so I can test them in unit tests. I'm starting to think fully decoupling them is not possible.

There is a decent amount of info out there, such as this question and this blog post. The blog post in particular gets you really far into mocking ObjectContext.

But my DomainServices have methods like this:

public IQueryable<Client> GetClients()
{
    return ObjectContext.Clients
        .Include("Foo")
        .Include("Bar")
        .Where(c => c.IsBaz);
}

It doesn't seem possible to fully mock the Include method, as it returns an ObjectQuery<T>, and the Include method is not captured in an interface anywhere (There is no IObjectQuery interface). ObjectQuery implements IQueryable<T>, and so I thought making my own Include method that returns IQueryable would work, but only if I plan to call Include at most once per query.

I am using EF4, .NET 4, Silverlight 4 and RIA Services RTW.

As a bit of a rant, I'm disappointed at how test unfriendly LINQ to Entities and by extension RIA Services is :(

+1  A: 

I don't think you should be unit testing at that level. I'm all in for unit tests, but there is a certain point where you need to stop.

Lets say that code is part of a ClientsLinqRepository, which in turns implements IClientsLinqRepository. You mock IClientsLinqRepository, when you implement code that depends on it.

While the above is perfectly valid, ClientsLinqRepository is an integration implementation. Pretty much like if you had IMessageSender and you implemented MailSender. Here you have code that its main responsibility is integrating with a separate system, for you that's the database.

Based on the above scenario, I suggest you do some focused integration tests on that class. So in that case you do want to hit the external system (database), and make sure that integration code is working appropriately with the external system. It'll allow you to quickly identify if anything in the code vs. the database is broken without dealing with the complexity of the rest of the system (which a pain when trying to do integration tests at other levels).

Keep the focused integration tests separate from the unit tests, so you can run the amazingly fast unit tests as much as you want, and run integration test when changes are made to any of the integration pieces and every now and then.

eglasius
Yeah making these tests be integration instead of unit tests is one solution and probably the one I will go with. Although I do feel like that is a bit of a cop out. My DomainService has methods that I want to unit test, that's a valid request. It's a real part of the app, as it decides what data to surface. Not to mention mocking DomainServices for the purpose of unit testing is a pretty common topic, it's just most examples use LINQ to SQL because it's more easily mocked.
Matt Greer
I missed that it was on your DomainService. Honestly I'm not well versed on DDD terms, but object retrieval from the DB doesn't seem like the sort of things a domain service is for / that said, people call service a lot of different things (in DDD or not). I'd look at it this way: do you have mixed the responsibility of object retrieval / database integration with other logic those domain services do? It might be the code asking you to pull out a responsibility. You'd get to test the actual logic in your unit tests, and the actual integration in the focused integration tests.
eglasius
A DomainService is the endpoint that the client calls to retrieve data from. But it doesn't do actual data retrieval, it is typically given an object to do the actual data retrieval (In the case of a DomainService that extends `LinqToEntitiesDomainService`, it asks its `ObjectContext` to go get the data out of the database using LINQ to Entities). ObjectContext is what I want to mock, so I can give the DomainService mock data during testing ...
Matt Greer
... It's the DomainService's job to do things like "This user asked for Clients, but I am only going to give it clients it is authorized to see", that's the functionality I want to test.
Matt Greer
I see, since the whole architecture / infrastructure you are using pushes hard for it to be that way my answer doesn't really help you. Being at the boundary, it'd already be a full integration test inside the boundary (of a thin layer though)
eglasius