Archive for February, 2010

Unit testing with Linq to SQL

Tuesday, February 9th, 2010

For some time we have been using Microsofts Linq-to-Sql as our ORM for several of our projects. It has some nice features, but it is also very different from just about every other ORM out there, and in a lot of ways it is troublesome and difficult to work with. One of the things that have been irritating me about Linq-to-Sql is that it’s central object, the datacontext, does not implement an interface and therefore it is difficult to unittest code that depends on it. Today I stumpled upon a solution to that problem in this post: http://www.iridescence.no/post/Linq-to-Sql-Programming-Against-an-Interface-and-the-Repository-Pattern.aspx

Let’s start with an example of the problem. We use a repository pattern to encapsulate our Linq our Linq-queries into manageable and reusable componentes. Are repository can look kindof like this:

public class ClipRepository : IClipRepository
{
    IDataContext _dc;
    IDataContext dc
    {
        get
        {
            if (_dc == null)
            {
                _dc = new PiratDataContext();
            }
            return _dc;
        }
    }
    public IQueryable getClipsFromSite(int siteid)
    {
        return from sc in dc.SiteClips
        where sc.SiteID == siteid
        select sc.Clip;
    }
}

The repository implements a IClipRepository interface an all code that depends on the repository is written against the interface instead of the specific implementation. This is great because we can then switch from a database backend-implementation to, say, a cachebackend without changing any of the dependant code. We use a servicelocator to provice simple inversion-of-control and break dependencies, and as a bonus, we can write unittest of code that uses the repository be reconfiguring the servicelocator to return a MockedClipRepository instead of the real one.

However, as mentioned above, the DataContext itself does not implement an interface, so we cannot use the servicelocator to provide the datacontext, and therefore we cannot really unittest our repositories without great difficulty.

Wrapping the DataContext

C# is flexible – with generics we can create a proxy class around a DataContext, and we can make that class implement a suitable interface.

public interface IDataContext
{
    /// <summary>
    /// Gets the repository for the given type of entities
    /// </summary>
    /// <typeparam name="T">The type of the entity</typeparam>
    /// <returns>The repository of the given type</returns>
    IQueryable<t> GetTable<t>() where T : class, INotifyPropertyChanged;
 
    /// <summary>
    /// Adds a new entity to the repository
    /// </summary>
    /// <typeparam name="T">The type of the entity</typeparam>
    /// <param name="item">The entity to add</param>
    void Insert<t>(T item) where T : class, INotifyPropertyChanged;
 
    /// <summary>
    /// Deletes the specified entity from the repository
    /// </summary>
    /// <typeparam name="T">The type of the entity</typeparam>
    /// <param name="item">The entity to delete</param>
    void Delete<t>(T item) where T : class, INotifyPropertyChanged;
 
    /// <summary>
    /// Commits the changes to the repository
    /// </summary>
    void Save();
 
    /// <summary>
    /// Provides access to the modified objects tracked by the system
    /// </summary>
    ChangeSet GetChangeSet();
}

And the wrapper class implementation for LinqToSql datacontexts:

public class LinqToSqlDataContext<t_datacontexttype> : IDataContext where T_DataContextType : DataContext
{
    private DataContext _dc;
    private DataContext dc
    {
        get
        {
 
            if (_dc == null)
            {
                _dc = Activator.CreateInstance<t_datacontexttype>();
            }
            return _dc;
        }
    }
 
    public IQueryable<t> GetTable<t>() where T : class, INotifyPropertyChanged
    {
        return dc.GetTable(typeof(T)).Cast<t>();
    }
 
    public void Insert<t>(T item) where T : class, INotifyPropertyChanged
    {
        dc.GetTable(typeof(T)).InsertOnSubmit(item);
    }
 
    public void Delete<t>(T item) where T : class, INotifyPropertyChanged
    {
        dc.GetTable(typeof(T)).DeleteOnSubmit(item);
    }
 
    public void Save()
    {
        dc.SubmitChanges();
    }
 
    public ChangeSet GetChangeSet()
    {
        return dc.GetChangeSet();
    }
 
    #endregion
}

Using the wrapper

With this wrapper in place, we then change the implementation of the repository above so that instead of creating it’s datacontext directly it uses the wrapper:

// creating the context
// in the ServiceLocators config, IDataContext is setup to return LinqToSqlDataContext<piratdatacontext>
_dc = ServiceLocator.New<idatacontext>();
 
// using it
return from sc in dc.GetTable<siteclip>()
         where sc.SiteID == siteid
         select sc.Clip;

Unit testing

With the above setup we can now create a MemoryDataContext, that has the same interface as the LinqToSql context, but does not depend on a database:

public class MemoryDataContext : IDataContext
{
    private readonly List<object> _inMemoryDataStore = new List<object>();
    private readonly IList<object> _Inserts = new List<object>();
    private readonly IList<object> _Deletes = new List<object>();
    private readonly IList<object> _Updates = new List<object>();
 
    public MemoryDataContext()
    {
    }
 
    public MemoryDataContext(params INotifyPropertyChanged[] objects) : this()
    {
        foreach (var obj in objects)
        {
            _insert(obj);
        }
    }
 
    private void _insert(INotifyPropertyChanged item)
    {
        _inMemoryDataStore.Add(item);
        item.PropertyChanged += new PropertyChangedEventHandler((s,e) => _Updates.Add(s));
    }
 
    public IQueryable<t> GetTable<t>() where T : class, INotifyPropertyChanged
    {
        var list = from objects in _inMemoryDataStore
                    where typeof(T).IsAssignableFrom(objects.GetType())
                    select objects as T;
        return list.AsQueryable();
    }
 
    public void Insert<t>(T item) where T : class, INotifyPropertyChanged
    {
        _insert(item);
        _Inserts.Add(item);
    }
 
    public void Delete<t>(T item) where T : class, INotifyPropertyChanged
    {
        _inMemoryDataStore.Remove(item);
        _Deletes.Add(item);
    }
 
    public void Save()
    {
        if (Completed != null)
        {
            Completed(this, EventArgs.Empty);
        }
    }
 
    public ChangeSet GetChangeSet()
    {
        var constructor = typeof(ChangeSet).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First();
        var changes = constructor.Invoke(new object[]
        {
            new ReadOnlyCollection<object>(_Inserts),
            new ReadOnlyCollection<object>(_Deletes),
            new ReadOnlyCollection<object>(_Updates),
        }) as ChangeSet;
 
        return changes;
    }
 
    public event EventHandler Completed;
}

And there – we can now write unittests of our repository methods like this:

        [TestInitialize]
        public void init()
        {
            var memContext = new MemoryDataContext();
            memContext.Insert(new Clip { ID = 1, Title = "hest" });
            memContext.Insert(new Clip { ID = 2, Title = "giraf" });
            memContext.Insert(new Clip { ID = 3, Title = "tobis" });
            memContext.Insert(new Clip { ID = 4, Title = "laks", AllowExternalEmbedding = true });
            memContext.Insert(new SiteClip { ClipID = 1, SiteID = 50 });
            memContext.Insert(new SiteClip { ClipID = 2, SiteID = 50 });
            memContext.Insert(new SiteClip { ClipID = 3, SiteID = 4 });
            memContext.Insert(new SiteClip { ClipID = 4, SiteID = 4 });
            memContext.Insert(new SiteClip { ClipID = 4, SiteID = 50 });
 
            ServiceLocator.Add(typeof(IDataContext), memContext);
        }
 
        [TestMethod]
        public void getClipsFromSiteTest()
        {
            ClipRepository target = new ClipRepository();
            Assert.AreEqual(3, target.getClipsFromSite(50).Count());
            Assert.AreEqual(2, target.getClipsFromSite(4).Count());
            Assert.AreEqual(0, target.getClipsFromSite(1).Count());
        }
    }