I claimed in a previous post that low coupling using dependency injection made the code base more testable – i.e. properly prepared for unit testing. Let’s dig a bit deeper into that assertion.
The ProductService
class is an obvious candidate for unit testing. It is a relatively small component with a well-defined responsibility (adhering to the Single Responsibility Principle). It is also properly isolated from its dependency (the repository), by an abstraction (the interface). Let’s create a unit test method for the ProductService
component:
[Fact]
public void GetDiscountedWillReturnDiscountedPrice()
{
var mockRepository = new MockRepository<IProduct>();
var productID = Guid.NewGuid();
var product = new Product(productID, "product1", 10.0m);
mockRepository.Add(product);
var productService = new ProductService(mockRepository);
var actual = productService.GetDiscounted(productID);
Assert.Equal<decimal>(product.Price * 0.95m, actual.Price);
}
This unit test method verifies that the ProductService
functionality for calculating a discounted price of a product works correctly. It follows the standard sequence of a unit test: First, it sets up the fixed baseline environment for the test (also called the test fixture). Then it exercises the system under test (in this case, the product service). Finally, it verifies the expected outcome. A “tear down” phase is not necessary, as the fixture objects automatically gets out of scope and will be garbage-collected.
As ProductService
does not care about the actual implementation of the product repository dependency, you can inject a “stand-in” for this dependency in the test. This stand-in is better known as a test double. The mockRepository
variable holds an instance of such a product repository test double.
In the final application, you are probably going to implement the repository so that the products are persisted in for example an SQL database, or maybe a file, but the elegant thing is that, at this moment in time, you do not need to care about this. In the context of the unit test, you can just make a mock implementation of the repository which does not implement persistence of the products at all, but just keep them in memory. This is our test double. Obviously, an implementation like this would never make it into the final application, but it is sufficient to test the ProductService
functionality in isolation.
Such a mock implementation of a repository is easily done. Of course, you make a generic version that can be used as test double for all entity repositories:
public class MockRepository<T> : IRepository<T> where T : IEntity
{
private Dictionary<Guid, T> entities = new Dictionary<Guid, T>();
public void Add(T entity)
{
this.entities[entity.Id] = entity;
}
public void Update(T updatedEntity)
{
this.entities[updatedEntity.Id] = updatedEntity;
}
public void Remove(T entity)
{
this.entities.Remove(entity.Id);
}
public IList<T> GetAll()
{
return this.entities.Values.ToList();
}
public T Get(Guid id)
{
var entity = default(T);
this.entities.TryGetValue(id, out entity);
return entity;
}
}
A Dictionary
object is used to hold the entities in memory during the test.
Testability is not necessarily the main purpose for doing dependency injection, but the ability to replace dependencies with test-specific mock objects is indeed a very useful by-product.
By the way, the unit test method above is written using the xUnit.net testing framework, which explains the Fact
attribute and the Equal
assertion. xUnit.net is a nice and very lean testing framework – compared to for example the MSTest, which is the one integrated with Visual Studio. With xUnit.net, you don’t need to create a specific test unit project. Also, you get rid of the auto-generated .vsmdi files and .testsettings files from MSTest.
To further refine and automate your unit tests, you should consider using supplementary unit test frameworks like AutoFixture and Moq to help you streamline fixture setup and mocking. Both are available from within the “NuGet Package Manager” Visual Studio Extension.