Ayende @ Rahien

Refunds available at head office

Awkward Testability for Linq for SQL

Note: I am writing this post in the express hope that it would help to change the current situation.

I have been just recently informed about a significant problem with Linq for SQL. It doesn't support testing easily. You can see the bug report here, and as usual, it was closed as Won't Fix without even providing an explanation. Did I mention already that I don't like Connect's approach to it? Yes, I did.

The root problem is a fairly simple one. Linq for SQL relies on reams of generated code, which means that if I have code line this:

var customers = from c in context.Customers 
  select c;

Then I have no really easy way to mock what is going on in here. The generated code provides a concrete class without an interface, so it is very hard to mock, and naturally, System.Data.Linq.Table<T> is sealed, has internal stuff, and is utterly hostile to mocking.

You have to use mocking if you  want to use the code above without hitting the database, and this is a core part of the way a lot of people work. Linq for SQL doesn't take that into account.

The requirement for mocking aren't really that onerous, we need and interface generated for the context class, and interface for Table<T> and that the generated code will default to using interfaces by default (Best Practices, anyone?).

Having to write adapters classes is something that TDDers on the .Net platform has become very familiar with, because of the insistence of using untestable practices throughout the framework. I am fairly tired of that, and I would certainly like to be able to skip this tiring process for once.

It is shocking to me that in this day and age the Linq for SQL team can still believe utterly ignore such practices and put hurdles in the path of easy testability.

Are we back to demos vs. reality development?

To finish, this bug has been closed, so it is not possible even to vote for it. At the very least, I would like to hear an explanation, and it had better not involved "lack of resources".

Comments

scottgu
08/16/2007 06:47 AM by
scottgu

If you are implementing data retrieval methods on your DataContext, you could implement an interface contract that you'd then use for Mocking your storage/retrieval logic:

interface IMyContract {

 void List<Customer> GetCustomersByState(string state);

}

public partial class NorthwindDataContext : IMyContract {

public List<Customer> GetCustomersByState(string state) {

         return Customers.Where(s=>s.State == state).ToList();

}

}

Ian Cooper has a good article on persistence ignorance and LINQ to SQL that is also very interesting: http://iancooper.spaces.live.com/blog/cns!844BD2811F9ABE9C!397.entry

Hope this helps,

Scott

Terence Lewis
08/16/2007 07:37 AM by
Terence Lewis

Perhaps you should check out http://aabs.wordpress.com/2007/06/26/using-mock-objects-when-testing-linq-code/. I haven't personally tried the approach he suggests, but from the glance I've taken through the article, it appears that he's found a way to pass canned results back from a DataContext without hitting the database.

Michael Hawksworth
08/16/2007 07:42 AM by
Michael Hawksworth

I have a lot of time for the LINQ team as they come from (are?) the Visual Foxpro Team where they contributed greatly to make my life easier. (far too many years working in vfp).

That said, given their isolation from the rest of the MS cosmos this could just be a lack of vision, either at concept or project level. It would not surprise me to find that MS had decided to reuse a lot of the existing C++ code without the overhead of much of a redesign.

Yes I know that that would be a poor excuse but like I said, I have a lot of time for the team that put it together.

I wouldn't mind the 'By Design' and just plain 'Closed' options on issues if they just explained why (well ok I would be it wouldn't cause my teeth to grate as much). Some of the teams involved in development (SBA UK for example) seem to understand feedback, pity the majority don't.

Baggins
08/16/2007 07:46 AM by
Baggins

I have an idea I haven't really thought through very well so it is probably flawed...

Can we not encapsulate queries in a Query object and unit test the query object against in-memory CLR collections? We then use an IRepository as the means for executing queries (we can mock out IRepository). The only thing we have not unit tested then is the implementation of IRepository, which is a single class.

We are of course assuming that the LINQ to SQL logic is correct in transforming a LINQ query to SQL, but with unit testing don't we want to make that assumption anyway? We're not testing the LINQ to SQL logic with any of our unit tests. We just want to test that our query logic is correct, which can be verified against an in-memory collection.

Mats Helander
08/16/2007 10:15 AM by
Mats Helander

This lack of interfaces is a real bummer with .NET imo. It makes mocking hard and it makes nice AOP hard. Bottom line: It makes it much harder to implement a whole lot of the traditional design patterns like decorator, proxy, etc.

What really worries me is the thought that this isn't just oversight or lack of budget, but instead a design "philosophy" at MS...

http://blogs.msdn.com/ericgu/archive/2007/02/01/properties-vs-public-fields-redux.aspx

http://blogs.msdn.com/jaybaz_ms/archive/2007/02/08/properties-vs-fields-again.aspx

"(Best Practices, anyone?)."

Is it the case that what the rest of the world considers best practice, MS considers worst practice?

/Mats

Roy Tate
08/16/2007 02:06 PM by
Roy Tate

I was able to vote for this issue even though it is closed.

josh
08/16/2007 03:23 PM by
josh

I see Scott Guthrie made a suggestion. Nice guy, always willing to help.. maybe he could cover it in his LinqToSQL blog series.

This, however, does ad to my reservations about LinqToSQL. I'm also concerned about what this will do to good design. I can almost hear the sound of 10,000 Morts marching toward maintenance hell. If think design and testing are two areas where Linq is falling short so far. Maybe we'll figure out a way to cover those. Maybe Rob Conery & SubSonic can make good design with Linq easier.. not sure about testing.

On another note, I learned that MS is including a build provider for nHibernate in .Net 3.5. I suppose for Linq.

Rant on Linq: Any time any one says the solution is to point & click, Drag-n-ndrop, and gui this or that for programming.. I cringe & cough!

Jeremy Gray
08/16/2007 03:28 PM by
Jeremy Gray

I do have to ask, are you trying to test your code that calls LINQ, LINQ itself, or LINQ correctly querying your database?

If "your code that calls LINQ", why does your code have a direct dependency on LINQ? Should you not abstract this behind an interface that can then be mocked so you can reasonably test your code?

If "LINQ itself", I'd have to ask what the benefit would be.

If "LINQ correctly querying your database", then it's an integration test where mocking cannot be involved.

I don't really see a fourth option, and unless I'm missing something the first option is the only one that makes any sense. It also happens to have a terribly easy solution.

Either I'm missing something or LINQ's mock-ability is a non-issue.

Judah
08/16/2007 04:16 PM by
Judah

I just wanted to say that it's very cool to see Scott Guthrie stop in and give a suggestion. I don't mean "cool" as in a celebrity dev is here commenting, but cool in that he's trying to help us down here in the bunkers, instead of watching silently from above the forest.

Cool guy. MS needs more Scott Guthries.

Evan
08/16/2007 06:02 PM by
Evan

This is pretty much a stab in the dark as I havent so much as even played with LINQ yet. Interfaces aren't the only way to loosely couple. Is there a delegate based solution lurking there somewhere?

Again, that question may not even make sense in the context of LINQ. I haven't looked very closely.

Speaking of delegates though, what about delegates and mocking..hmm..I think I'll have to dig into that a bit more to see where that thought leads..

Mats Helander
08/16/2007 06:07 PM by
Mats Helander

Jeremy,

In the special case of mockability I would agree with you, but in the more general case, surely substitutability is an important thing, even for linq2sql ?

The big problem is that MS code too often binds directly to classes (often sealed) instead of to interfaces and that the standard is to new up objects instead of creating them via factories. This means that the substitutability leaves a bit to be desired in large parts of the framework.

The somewhat smaller problem is that, when there's no interfaces, my code will either have to bind to their classes, or I have to create my own interfaces plus adapter classes. Sure, that's doable, but I wouldn't have to do that if MS had provided interfaces that their classes already implemented instead. Then my alternative implementations could just implement the same interface and I wouldn't have to write the interface nor the adapter to the MS class.

I think this problem is one of the sad parts of the otherwise fantastic .NET framework.

/Mats

Evan
08/16/2007 08:18 PM by
Evan

@Mats,

I'm not defending the MS camp on this one, but the following article at least gave me better insight as to why they do that..

http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarvj/html/msdn_deltruth.asp

Pedro Teixeira
08/16/2007 10:52 PM by
Pedro Teixeira

I guess this is another motivation towards using Linq To NHiberate (?)

Brent Brown
08/16/2007 11:33 PM by
Brent Brown

@scottgu: Thanks for posting. That's a cool pattern. I did a quick prototype, using a windsor container to locate and return the data context under the IMyContract interface. Works well and can be mocked.

That being said, the generated code really should be following best practices and use interfaces. There's no reason (IMHO) not too, and it gives developers much more freedom in their architecture.

Michael Hawksworth
08/17/2007 07:33 AM by
Michael Hawksworth

Given the time it takes for an organisation such as MS to make such a huge strategic decision as .Net it may well be that they are using best practice as was determined at that time; and once down a road like that it is very hard to turn onto another track.

There is also a corporate protective mechanism in play here. For example the sealed classes are there to help stop other parts of a development track from bastardising part of the core system.

I am new to working using tdd,xp etc. techniques and I still find myself falling into lots of old habits and then having to review everything, it is even worse when I learn a new approach to something (good learning curve though). MS's whole original framework mostly uses older techniques and the teams (I would assume) aren't in a position to look at applying new approaches. I am not saying they are blind to them (many of their new applications use them) just that they can't find a point when they could realistically implement them. .Net 5 anyone?

Comments have been closed on this topic.