In previous post I talked about building domain model for Northwind using Entity Framework. I used the generated ObjectContext class which is NorthwindDataContext as the basic Data Access Layer Helper. But my NorthwindDataContext is implementing custom interface I created in order to be able to make my Data Access Layer testable as well as to be independent from EF.
My target is to achieve decoupling between EF and all layers above it. This include Repository classes. This might not be of a big benefit to everyone, because my repositories implementation will still dependent on IDataContext. But for me it will help to do TDD more smoothly regardless of my underlying data access layer. Beside it might allow me in future to be able to switch from EF to LINQ to SQL and just implement my IDataContext interface for LINQ to SQL.
Source code is attached to this post, feel free to download it and explore the code
Refactoring of IDataContext and Models:
After some discussion with Davy Landman and getting some feedback from him. I made some refactoring to IDataContext although me an him have different ideas few conflicts. So basically here is what I need from IDataContext to support:
- Being able to retrieve all records of Categories, Products & Supplier
- Being able to filter these entities by unique identifier to return single record.
- Being able to register new record for insertion or deletion.
- Being able to persist changes to the underlying data source.
The above list is not the only features I need, in future it must grow, like being able to filter Products by category and so on.
I hear some of you saying, What! this is the job of repository classes to build queries and perform data access. and I will answer yes and I totally agree, but I provide IDataContext as a huge repository of queries that is actually wrapped by Repositories. I think this concept is applicable with some ORM tools such as EF and LINQ to SQL. But might not work for other things such as classical Data Access Layers and NHibernate. So simply I consider IDataContext part of repository classes.
The implementation of IDataContext which is NorthwindDataContext class includes some helper methods & properties to assist me to work with EF and being able to test it. But generally I am hiding most the features provided by EF generated code with these methods & properties. Because simply that generated code is not testable and you cannot use to set expectations when you start to use mocking.
Below is the class diagram of my refactored model with data access layer helper:
Also I zoomed on NorthwindDataContext class and provided the following diagram with comments about 2 helper methods:
Here is the code of GetEnumrable & GetFilteredDataSource methods with explanation of each method's role :
/// <summary>
/// Used as helper method to provide casting from EDM primative data type
/// (generated model entity) such Category to model Interface such ICategory
/// which is implemented by the generated EDM entity class.
/// </summary>
/// <typeparam name="I">Type of the model interface</typeparam>
/// <typeparam name="E">
/// Type of the EF primative data type (generated model entity)
/// "E" (model entity) must implement "I" (model interface)
/// </typeparam>
/// <param name="datasource">Data source to be filtered</param>
/// <param name="filter">Filter expression used to filter datasource</param>
/// <returns>IEnumrable of I (model interface)</returns>
private static IEnumerable<I> GetEnumerable<I, E>(IQueryable<E> datasource, Expression<Func<E, bool>> filter)
where I : class, IEntity
where E : class, I
{
var filteredQry = (filter != null) ? datasource.Where(filter) : datasource;
foreach (var item in filteredQry)
yield return item as I;
}
/// <summary>
/// Takes as a parameter Linq filter Expression to be used to filter data source set
/// such as CategorySet.
/// This will help to provide filters that will perform filtering on underlying
/// data source side and not in memory filtering using LINQ to Objects.
/// This method calls GetEnumerable<I, E> providing it with the correct data source set.
/// </summary>
/// <typeparam name="I">Type of the model interface</typeparam>
/// <typeparam name="E">
/// Type of the EF primative data type (generated model entity)
/// "E" (model entity) must implement "I" (model interface)
/// </typeparam>
/// <param name="datasource">Data source to be filtered</param>
/// <param name="filter">Filter expression used to filter datasource</param>
/// <returns>IQueryable of I (model interface)</returns>
private IQueryable<I> GetFilteredDataSource<I, E>(Expression<Func<E, bool>> filter)
where I : class, IEntity
where E : class, I
{
IQueryable<E> queryable = null;
if (typeof(I) == typeof(ICategory))
queryable = this.CategoryTable as IQueryable<E>;
else if (typeof(I) == typeof(IProduct))
queryable = this.ProductTable as IQueryable<E>;
else if (typeof(I) == typeof(ISupplier))
queryable = this.SupplierTable as IQueryable<E>;
return GetEnumerable<I, E>(queryable, filter).AsQueryable() as IQueryable<I>;
}
Now if I wanted to filter Category set data source by Category Id I shall use GetFilteredDataSource by a way or another. That is why I am using in FindXxxById methods as the following:
public ICategory FindCategoryById(int id)
{
return this.GetFilteredDataSource<ICategory, Category>(c => c.Id == id).FirstOrDefault();
}
public IProduct FindProductById(int id)
{
return this.GetFilteredDataSource<IProduct, Product>(c => c.Id == id).FirstOrDefault();
}
public ISupplier FindSupplierById(int id)
{
return this.GetFilteredDataSource<ISupplier, Supplier>(c => c.Id == id).FirstOrDefault();
}
Coding Testable EF ObjectContext Class:
Before starting this point, let me clarify what does Testable means. Testable means being able to write Unit Test code that can test your coded independent of the environment, database or anything similar. And being able to mock your classes. Simply being able to write Unit Tests that follow the guide lines mentioned in my earlier post.
That was my target for NorthwindDataContext class, is being able to test it independent of the database, and confirm the success of my test by doing integration test that also success.
Someone would say and why I want to test something that is already tested (by Microsoft)? Well because later you'll need to test classes that depends on this class. If you used the auto generated EF code as it is you'll not be able to set expectations out of it to be able to test independent of the database.
The auto generated code is not testable, because it requires to hit the database and you cannot mock it. In order to be able to stay way of database you need to have an alternative. The common alternative is in memory storage using Lists.
Using generated EntitySet types such as CategorySet properties will require database. To workaround this I created few helper public virtual properties that wrap auto generated EntitySets such as CategorySet as the following:
/// <summary>
/// Return EF Coupled IQueryable of Category. Used for testing purpose only
/// </summary>
public virtual IQueryable<Category> CategoryTable
{
get{ return this.CategorySet; }
}
/// <summary>
/// Return EF Coupled IQueryable of Product. Used for testing purpose only
/// </summary>
public virtual IQueryable<Product> ProductTable
{
get{ return this.ProductSet; }
}
/// <summary>
/// Return EF Coupled IQueryable of Supplier. Used for testing purpose only
/// </summary>
public virtual IQueryable<Supplier> SupplierTable
{
get{ return this.SupplierSet; }
}
Now why these helper properties are virtual public. Public because I want to be able to access them throw my unit test classes. Virtual because Moq will require these properties to be virtual in order to be able to override them to return my specified expectations. Same concept is applied to some public methods such AddObject and DeleteObject methods. But because these properties or methods are not interface based; so they will not be accessible through repositories as I'll show on my upcoming posts.
The above properties are invoked through GetFilteredDataSource method.
Writing Unit Test for EF ObjectContext Class:
In TDD is supposed to write your tests first, fail them and start fix what is broken and make your code run. Then do refactoring over and over to formalize your code. My first test attempt will fail because of NotImplementedException thrown by IDataContext methods in NorthwindDataContext class. My test basically want to test the following:
- Calling IDataContext (Categories,Products or Suppliers) properties should not return null.
- Calling IDataContext FindXxxById methods should return correct Xxx entity.
- Calling IDataContext Insert should call AddObject Method of NorthwindDataContext to insure that the object is being added to proper Entity Set and prepared for persistence.
- Calling IDataContext Delete should call DeleteObject Method of NorthwindDataContext to insure that the object is being marked for deletion.
I am using Moq to assist me in my TDD. In this attempt I will need a mock instance of NorthwindDataContext to simulate its work independent of the underlying database. Also I am initializing few lists to act as in memory storage.
private readonly Mock<NorthwindDataContext> _context;
private readonly List<Category> _categories;
private readonly List<Product> _products;
private readonly List<Supplier> _suppliers;
public NorthwindDataContextTest()
{
_context = new Mock<NorthwindDataContext>(ConfigurationManager
.ConnectionStrings["NorthwindDataContext"]
.ConnectionString);
_categories = new List<Category>();
_products = new List<Product>();
_suppliers = new List<Supplier>();
}
On line 7 to 9 I am initializing new mock instance of NorthwindDataCotext. I am getting a connection string from application configuration file. you might ask why I would need a connection string. Actually this is a must thing in order to be able to load your EDM model. If you didn't provide this connection string the mocking will fail. The connection string in this test just specify the path of CSDL, SSDL and MSL. In my case they are embedded resources:
<configuration>
<connectionStrings>
<add name="NorthwindDataContext"
connectionString="metadata=res://*/Edm.Northwind.csdl|res://*/Edm.Northwind.ssdl|res://*/Edm.Northwind.msl;provider=System.Data.SqlClient;"
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
Writing Test Initialization with Visual Studio.Net Unit Testing:
As I am using VS.Net unit testing tools I am using the provided unit testing facilities. Such as Test Initialization feature. This is just a method marked with TestInitialize attribute as the following:
[TestInitialize]
public void TestInitialize()
{
_context.ExpectGet(c => c.CategoryTable)
.Returns(_categories.AsQueryable());
_context.ExpectGet(c => c.ProductTable)
.Returns(_products.AsQueryable());
_context.ExpectGet(c => c.SupplierTable)
.Returns(_suppliers.AsQueryable());
}
I am using the mocked NorthwindDataContext to set expectation to the helper properties I mentioned earlier. Again to be able to set expectations with Moq you'll need public virtual methods or properties. So Whenever a call is made to CategoryTable or ProductTable or SupplierTable the specified expectation for each one should be returned.
Now I want to test IDataContext properties (Categories, Products and Suppliers):
[TestMethod]
public void NorthwindDataContext_Categories_Returns_Category_Set()
{
Assert.IsNotNull(_context.Object.Categories);
}
[TestMethod]
public void NorthwindDataContext_Products_Returns_Product_Set()
{
Assert.IsNotNull(_context.Object.Products);
}
[TestMethod]
public void NorthwindDataContext_Suppliers_Returns_Supplier_Set()
{
Assert.IsNotNull(_context.Object.Suppliers);
}
Hint: Unit test method in VS.Net are marked with TestMethod Attribute. Every time one of the test method being call TestInitialize Method will be called first as well as test class constructor.
I wish to trace the call of the above code. _context.Object will return a mocked instance of NorthwindDataContext. When calling Categories property this property invokes GetFilteredDataSource method which in turn calls CategoryTable helper property. When CategoryTable property is called it will return the expectation I specified in TestInitialize method. Same thing is applied for the other properties.
Time to test filtering methods such as FindCategoryById. I write test for such methods by preparing data to test with, then set my expectation. Finally perform tested method call and assert returned results, just as the following:
[TestMethod]
public void NorthwindDataContext_FindCategoryById_Returns_Correct_Category()
{
//Prepare test data.
for (int i = 1; i < 11;i++ )
_categories.Add(Category.CreateCategory(i, String.Empty));
//Get existing Id.
int id = _categories[0].Id;
//Set Expectation
_context.ExpectGet(c => c.CategoryTable).Returns(_categories.AsQueryable());
//Perform Test
var category = _context.Object.FindCategoryById(id);
//Assert
Assert.IsNotNull(category);
Assert.AreEqual(category.Id, id);
}
Note that I am testing single thing per unit test method. That doesn't mean you should perform single assert. In the above method I am testing one then but doing 2 asserts, one to ensure that the returned result is not null and the other assert to ensure that the returned result is the desired one.
My final test is to test Insert and Delete method. Actually in these tests I just want to check if AddObject or DeleteObject methods are being called. These helper methods uses internally the EF generated code to add object to the context or mark it for deletion. I don't want to test the underlying EF generated code, I just want to test mine.
[TestMethod]
public void NorthwindDataContext_Insert_Should_Call_AddObject()
{
var category = Category.CreateCategory(1, String.Empty);
_context.Expect(c => c.AddObject(category)).Verifiable();
_context.Object.Insert(category);
_context.Verify();
}
On line 6 I am setting my expectation as a call to AddObject method. And using Moq features I am asking to verify this call using Verifiable method of the mocked object. On line 10 I am calling Verify method of the mocked NorthwindDataContext. This method will verify that AddObject method is being called. If not the test will fail.
After running my test I got them all Green.That was a happy ending. This code should run on your machine and produce green results as well. You can download it and review the code and get me with some feedback and enhancements.
I would like to thank Davy Landman for his valuable ideas and thoughts and the references he provided me about DDD. I am new to DDD & TDD and I thought the best way to learn them is to share my thoughts with others, making mistakes and let somebody else correct me. So feel free to comment and provide me with more resources and ideas.
As this post already grow long I will keep my integration test for the next post. Where I will show that my assumptions and expectations during TDD unit testing applies also on my Integration testing.
Keep tuned.