Thursday, February 19, 2009 10:32 PM
Kazi Manzur Rashid
100% Unit Testable Linq To Sql Repository
In this post, I will show you how you can architect your Linq To Sql repository which will have 100% code coverage. I will use our favorite Northwind database along with my ongoing UnityCommonServiceLocatorMVC project. First, lets add a Linq To Sql diagram in our project and drag n drop the Category and Product table that will look something like following:

We are skipping the other tables as they are same that we are doing here, next we will create an interface which will abstract the Linq To Sql DataContext from our application.
public interface IDatabase : IDisposable
{
IQueryable<Category> CategoryDataSource
{
get;
}
IQueryable<Product> ProductDataSource
{
get;
}
IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class;
ITable GetEditable<TEntity>() where TEntity : class;
void Insert<TEntity>(TEntity instance) where TEntity : class;
void InsertAll<TEntity>(IEnumerable<TEntity> instances) where TEntity : class;
void Delete<TEntity>(TEntity instance) where TEntity : class;
void DeleteAll<TEntity>(IEnumerable<TEntity> instances) where TEntity : class;
void SubmitChanges();
}
And now we will create a partial class for the DataContext that implements this interface:
public partial class Database : IDatabase
{
public IQueryable<Category> CategoryDataSource
{
get
{
return GetQueryable<Category>();
}
}
public IQueryable<Product> ProductDataSource
{
get
{
return GetQueryable<Product>();
}
}
public virtual IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class
{
return GetTable<TEntity>();
}
public virtual ITable GetEditable<TEntity>() where TEntity : class
{
return GetTable<TEntity>();
}
public void Insert<TEntity>(TEntity instance) where TEntity : class
{
GetEditable<TEntity>().InsertOnSubmit(instance);
}
public void InsertAll<TEntity>(IEnumerable<TEntity> instances) where TEntity : class
{
GetEditable<TEntity>().InsertAllOnSubmit(instances);
}
public void Delete<TEntity>(TEntity instance) where TEntity : class
{
GetEditable<TEntity>().DeleteOnSubmit(instance);
}
public void DeleteAll<TEntity>(IEnumerable<TEntity> instances) where TEntity : class
{
GetEditable<TEntity>().DeleteAllOnSubmit(instances);
}
}
By Default Linq To Sql exposes Sql Server tables as Table which is a sealed class, so we can not mock it. But Linq to Sql namespace has an interface ITable which the Table type implements and that is easy to mock, also this ITable exposes InsertOnSubmit, InsertAllOnSubmit, DeleteOnSubmit, DeleteAllOnSubmit. So in our interface we exposed the ITable instead of that concrete Table which makes this partial Database class completely unit testable. We will be using Moq and xUnit.net for our unit tests but you can also use RhinoMock, NUnit, MBUnit or MSTest.
public class DatabaseTest
{
private readonly Mock<Database> _database;
public DatabaseTest()
{
_database = new Mock<Database>("A dummy connection string");
}
[Fact]
public void CategoryDataSource_Should_Call_GetQueryable()
{
_database.Expect(d => d.GetQueryable<Category>()).Returns(new List<Category>().AsQueryable()).Verifiable();
Assert.NotNull(_database.Object.CategoryDataSource);
_database.Verify();
}
[Fact]
public void ProductDataSource_Should_Call_GetQueryable()
{
_database.Expect(d => d.GetQueryable<Product>()).Returns(new List<Product>().AsQueryable()).Verifiable();
Assert.NotNull(_database.Object.ProductDataSource);
_database.Verify();
}
[Fact]
public void Insert_Should_Call_GetEditable_And_InsertOnSubmit()
{
var editable = new Mock<ITable>();
var category = new Category();
_database.Expect(d => d.GetEditable<Category>()).Returns(editable.Object).Verifiable();
editable.Expect(e => e.InsertOnSubmit(category)).Verifiable();
_database.Object.Insert(category);
_database.Verify();
editable.Verify();
}
[Fact]
public void InsertAll_Should_Call_GetEditable_And_InsertAllOnSubmit()
{
var editable = new Mock<ITable>();
var categories = new List<Category>();
_database.Expect(d => d.GetEditable<Category>()).Returns(editable.Object).Verifiable();
editable.Expect(e => e.InsertAllOnSubmit(categories)).Verifiable();
_database.Object.InsertAll(categories);
_database.Verify();
editable.Verify();
}
[Fact]
public void Delete_Should_Call_GetEditable_And_DeleteOnSubmit()
{
var editable = new Mock<ITable>();
var category = new Category();
_database.Expect(d => d.GetEditable<Category>()).Returns(editable.Object).Verifiable();
editable.Expect(e => e.DeleteOnSubmit(category)).Verifiable();
_database.Object.Delete(category);
_database.Verify();
editable.Verify();
}
[Fact]
public void DeleteAll_Should_Call_GetEditable_And_DeleteAllOnSubmit()
{
var editable = new Mock<ITable>();
var categories = new List<Category>();
_database.Expect(d => d.GetEditable<Category>()).Returns(editable.Object).Verifiable();
editable.Expect(e => e.DeleteAllOnSubmit(categories)).Verifiable();
_database.Object.DeleteAll(categories);
_database.Verify();
editable.Verify();
}
}
Now, lets create the repositories which uses this database:
public interface IProductRepository
{
void Add(Product product);
void Remove(Product product);
Product FindById(int id);
ICollection<Product> FindByCategoryId(int categoryId);
ICollection<Product> FindAll();
}
public class ProductRepository : IProductRepository
{
private readonly IDatabase _database;
public ProductRepository(IDatabase database)
{
_database = database;
}
public void Add(Product product)
{
_database.Insert(product);
}
public void Remove(Product product)
{
_database.Delete(product);
}
public Product FindById(int id)
{
return _database.ProductDataSource.SingleOrDefault(p => p.ProductID == id);
}
public ICollection<Product> FindByCategoryId(int categoryId)
{
return _database.ProductDataSource.Where(p => p.CategoryID == categoryId).ToList().AsReadOnly();
}
public ICollection<Product> FindAll()
{
return _database.ProductDataSource.ToList().AsReadOnly();
}
}
And the Unit Test for it:
public class ProductRepositoryTest
{
private readonly Mock<IDatabase> _database;
private readonly ProductRepository _repository;
public ProductRepositoryTest()
{
_database = new Mock<IDatabase>();
_repository = new ProductRepository(_database.Object);
}
[Fact]
public void Add_Should_Use_Database()
{
_database.Expect(db => db.Insert(It.IsAny<Product>())).Verifiable();
_repository.Add(new Product());
_database.Verify();
}
[Fact]
public void Remove_Should_Use_Database()
{
_database.Expect(db => db.Delete(It.IsAny<Product>())).Verifiable();
_repository.Remove(new Product());
_database.Verify();
}
[Fact]
public void FindById_Should_Return_Correct_Product_For_The_Specified_Id()
{
SetupProductDataSource();
var product = _repository.FindById(3);
Assert.Equal(3, product.ProductID);
}
[Fact]
public void FindByCategoryId_Should_Return_Correct_Products_For_The_Specified_CategoryId()
{
SetupProductDataSource();
var products = _repository.FindByCategoryId(2);
Assert.Equal(2, products.Count);
}
[Fact]
public void FindAll_Should_All_Products()
{
SetupProductDataSource();
var products = _repository.FindAll();
Assert.Equal(5, products.Count);
}
private void SetupProductDataSource()
{
Category category1 = new Category{ CategoryID = 1, CategoryName = "Test 1" };
Category category2 = new Category{ CategoryID = 2, CategoryName = "Test 2" };
List<Product> products = new List<Product>
{
new Product{ ProductID = 1, ProductName = "Test 1", Category = category1},
new Product{ ProductID = 2, ProductName = "Test 2", Category = category1},
new Product{ ProductID = 3, ProductName = "Test 3", Category = category1},
new Product{ ProductID = 4, ProductName = "Test 4", Category = category2},
new Product{ ProductID = 5, ProductName = "Test 5", Category = category2}
};
_database.ExpectGet(db => db.ProductDataSource).Returns(products.AsQueryable());
}
}
I am skipping the CategoryRepository as it almost identical, but you can check it in the attached source codes at the bottom of this post. If I run these unit tests with NCover, we will get the following picture:
As you can see that we have 100% coverage for both ProductRepository and CategoryRepository but 56% for the Database. But if you look it closely, you will find that we have 100% code coverage except the GetQueryable and GetEditable methods in the Database partial class .
Download: LinqTSqlCodeCoverage.zip
Filed under: C#, LINQ to SQL, Code Coverage, Mock, Unit Test