As promised, more details on my Tumblr post from yesterday.
The inherent difficulties in automated testing of database-dependent code are well known. For pure unit tests, which exercise minimal chunks of functionality and definitively ignore dependencies, the issue is less important. But for behaviour tests, which exercise components at a higher level, the ability to provide mock data is extremely useful. Unfortunately, where your data access code directly addresses a specific data store, substituting some form of mock or stub for that store is labour-intensive, and can significantly impact the performance of your tests if a database must be set-up and torn-down each time a test or suite of tests is run.
Simple.Data provides a level of abstraction from the underlying data store, since it works without requiring the developer to write any actual SQL code, or to generate code or construct mappings to link objects to the store. Instead, it uses a convention-over-configuration approach to translate dynamic constructs in C# code into operations against a data store, which may or may not be a relational database.
The Adapter model
I recently refactored the internals of Simple.Data so that all store-related operations are provided by an implementation of a basic interface. An implementation which uses ADO.NET classes and generates SQL is included with the library, but it is possible to write your own adapters to persist data to anything you like. I plan to create an adapter for working with Windows Azure Table Storage.
We can provide this functionality because of the way data operations are specified when using Simple.Data. For example, to find a User given an email address and password, we can use this code:
var user = db.Users.FindByEmailAndPassword(email, password);
When the ADO adapter is used against a SQL Server database, this will be translated into a parameterised SQL statement:
select * from [Users] where [Email] = @p0 and [Password] = @p1
An alternate adapter, though, could turn the exact same call into anything it wanted. For example, against Azure, we could produce an OData-style REST query:
https://odatasource.net/Users?$filter=Email eq ‘foo’ and Password eq ‘bar’
OK, so that’s cool and fun and possibly moderately useful, right? If you were writing a system to be back-end agnostic, that might be handy. But it also means we can write a very, very basic adapter which works in-memory, just to satisfy data-store dependencies in testing.
The system I’m working on has a feature that will send a password reminder to people if they forget theirs. There is a controller action which takes an email address, looks for a matching user, and sends an email or displays a message accordingly. The controller talks to a Data Access Layer, which is in turn using Simple.Data to talk to a SQL Server database. I wanted some way that the controller could talk to the Data Access Layer, which would talk to Simple.Data, and get served some testing data without needing SQL Server to be present.
So, I threw together an implementation of IAdapter which took an XML string in its constructor, and translated Find operations into LINQ-to-XML queries. The meat of the implementation is this:
[sourcecode language="csharp"]public IEnumerable> FindAll(string tableName, IDictionary criteria)
var query = GetTableElement(tableName).Elements();
foreach (var criterion in criteria)
var column = criterion.Key;
var value = criterion.Value;
query = query.Where(xe => xe.TryGetAttributeValue(column).Equals(value.ToString()));
return query.Select(e => e.AttributesToDictionary());
I also added a Mocking library to the solution, which provides access to some internals of the main assembly, and allows you to override the database connection process and inject your own database or adapter to be used instead. The code to do that looks like this:
@"<data><Users><User Email=""foo"" Password=""bar""/></Users></data>"));[/sourcecode]
Which is very simple, and straightforward, and most importantly is short enough to include within the code of the test itself. That one line of code, and when my Users DAL object goes to Simple.Data and says "give me my database", instead of the SQL Server 2008 R2 Enterprise instance which the config tells it to connect to, it will just return this little object with its one XML element which is enough to satisfy the criteria we’re using for the test.
I think that’s pretty cool. You’re welcome to disagree in the comments.
OK, the main issue that’s outstanding is that at present the data access of which the library is capable is a tad limited; most importantly, it can’t do joins. And with all this great stuff that I can do with the abstraction, I don’t want to have to fall back to SQL to achieve something really pretty common like that. So, here’s what I’m thinking.
[sourcecode language="csharp"]var activeCustomersInLondon = db.Customers
.Where(Active: "Y", CA_Town: "London")
That’s the most succinct syntax I can think of. Any good? Any better ideas?