Unit testing is a tricky beast. As with all unit tests it is important to abstract any dependencies – so in the case of EF it’s the data persistence source. This must be handled carefully because EF creates a container that performs the interaction with the data source. Abstracting that container (via mocking or stubbing) is painfully hard.
There are a few approaches for doing this, this is mine. I’m using the following tooling:
- Visual Studio 2010
- Microsoft Moles
The pattern uses the following DAL class structure (like a pseudo-repository pattern against an example Product database table):
- ProductDataRepository: IProductDataRepository – A thin layer to EF, returns only IQueryable<T> types from raw EF lookups. No filters, sorting or other queries are applied here as this class is not unit testable. This class internal to the DAL assembly.
- ProductRepository(IProductDataRepository repos) – Constructor takes an instance of the IProductDataRepository interface, and uses this for executing specific queries against the base IQueryable<T> values. This class is exposed for external calls. This is unit-tested.
The unit tests can use Moles to stub an instance of IProductDataRepository (delegating the internal Get… methods to returning a hard coded collection of test data) that is then passed to the instance of ProductRepository under test.
Looking at these classes from the bottom up, as an example:
ProductDataRepository
To Provide a thin layer that only returns IQueryable<T> (our example also filters out deleted rows):
public class ProductDataRepository : IProductDataRepository { private MyDatabase_ModelContainer _model; protected internal MyDatabase_ModelContainer Model { get { if (_model == null) { _model = new MyDatabase_ModelContainer(); } return _model; } } public IQueryable<product> GetProducts() { return Model.Products.Where(p => p.IsTombstoned == false); } }
ProductRepository
Core Requirement: Provide the implementation and execution of the data lookup. Consumes an instance of IProductDataRepository via constructor injection.
public class ProductRepository{ public ProductRepository() : this(new ProductDataRepository()) { } private IProductDataRepository _mRepository; public ProductRepository (IProductDataRepository iRepository) { _mRepository = iRepository; } public Product GetProduct(String productKey) { return mRepository.GetProducts().FirstOrDefault(p => p.ProductKey.Contains(productKey)); } public List<Product> GetProducts (String productKey) { return mRepository.GetProducts().Where(p => p.ProductKey.Contains(productKey).ToList(); } }
So, we can see that this is making calls to:
mRepository.GetProducts()
Which returns an IQueryable<T> type, then implements required functionality:
.Where(p => p.ProductKey.Contains(productKey))
and then executes the Query:
.ToList();
GetProductsTest
To test the ProductRepository we can create an instance and stub the IProductDataRepository. The mocking and standard setup is done in an abstract class, to provide reusability for other tests that may require this functionality:
[TestClass] public abstract class ProductTestContext { private IProductDataRepository _dataRepository; private IProductDataRepository DataRepository { get { if (_dataRepository == null) { _dataRepository = new SIProductDataRepository() { GetProducts = () => Products.AsQueryable() }; } return _dataRepository; } } private ProductRepository _ProductRepository; public ProductRepository ProductRepository { get { if (_ProductRepository == null) { _ProductRepository = new ProductRepository(DataRepository); } return _ProductRepository; } } private List<Product> _products; public List<Product> Products { get { if (_products == null) { _products = new List<Product>(); for (int i = 1; i <= 9; i++) { _products.Add(new Product(){ ProductKey = "MyKey" + i }); } } return _products; } } }
So, here we can see the IProductDataRepository readonly property returns a stubbed instance new new SIProductDataRepository. This stub is generated by the Moles framework (see Moles documentation on how to do this). During the creation of this object we delegate the GetVouchers method to return the concrete (and hardcoded because this is a unit test) instance of Products.AsQueryable():
GetProducts = () => Products.AsQueryable()
The .AsQueryable() is essential to insure the instance of IProductDataRepository, that is used during the test, returns the same type as the instance used in the real world.
Now our unit test class can inherit ProductTestContext and Arrange, Act and Assert the required tests easily:
[TestClass] public class GetProductsTest : ProductTestContext { [TestMethod] public void GetSingleProduct() { // repeat the test for each instance in the test collection for (int i = 1; i <= 9; i++) { // Arrange – most of the Arrangement is done in the abstract var goodProductKey = "MyKey" + i; // Act – on the abstract instance of ProductRepository, // which is using the stubbed instance of ProductDataRepository var product = ProductRepository.GetProduct(goodProductKey); // Assert – make sure the correct product is returned from the stubbed data Assert.IsNotNull(product); Assert.IsTrue(product.ProductKey.Equals(goodProductKey)); } } [TestMethod] public void GetIndivudalFilteredProducts() { // repeat the test for each instance in the test collection for (int i = 1; i <= 9; i++) { // Arrange – most of the Arrangement is done in the abstract var goodProductKey = "MyKey" + i; // Act – on the abstract instance of ProductRepository, // which is using the stubbed instance of ProductDataRepository var products = ProductRepository.GetProducts(goodProductKey); // Assert – make sure the correct products are returned from the stubbed data Assert.IsNotNull(products); Assert.IsTrue(products.Count == 1); } } [TestMethod] public void GetMultipleFilteredProducts() { // Expectation – all the results in the abstract Products collection should be returned var expectation = Products.Count; // Arrange – most of the Arrangement is done in the abstract var goodProductKey = "MyKey"; // Act – on the abstract instance of ProductRepository, // which is using the stubbed instance of ProductDataRepository var products = ProductRepository.GetProducts(goodProductKey); // Assert – make sure the correct products are returned from the stubbed data Assert.IsNotNull(products); Assert.AreEqual(expectation, products.Count); } }
Good to see your using TDD. I’ve been falling in love more and more with TDD as I gradually comprehend its concept and begin to grow more used to it. Automatically viewing my software from the “tester” perspective and not only from the developer perspective.
And I’m glad to see that someone that does Unit Tests, talks bad about it :D. What i mean its, only when you truly know something, you know the goods and the bads about it, kinda like marriage :D. What made me read your post was the “Unit testing is a tricky beast” part. 😀
Here at work we don’t use the Microsoft Entity Framework, we have a custom implementation to deal with the Persistence Layer (basically because we use a in-house MDA Tool that generates code for all layers), and in our unit tests we’ve used Moq (http://code.google.com/p/moq/) as our Mocking Library and NUnit (http://www.nunit.org/) as our Testing Framework.
I’ve read about Moles and found it amazing! It’s the first Mocking Library i’ve met that without Dependecy Injection, wrappers, etc, makes it possible to replace the executed code of a compiled property, method, etc. But i don’t know yet it it will work well with NUnit. Do you have any experiences to share about that?
I’m preparing some posts about this journey, specially the difficult parts and problems I’ve been bumping into. Some of them have solutions, some other I’ve implemented solutions and unfortunately some of them i think require some major shift at how programming languages are made (lets face it, they are not well prepared for tests)
Hi Rafael.
Glad you found the post useful. The stubbing / mocking is abstracted from the test so I see no reason why this approach wouldn’t work with NUnit (or MBUnit). Having said that I haven’t tried it. If you get a chance to run this pattern I’d be interested to hear you experiences.
I avoided Moq, TypeMoq and JustMock due to compatibility issues with companies that won’t release budget for this type of testing – and there are too many of them I’m afraid 😦
Regards,
Mark