Lately I have been researching how to best unit test an EF repository and given a properly tested repository, what to test in the controller.
My research did not reveal many sound examples or explanations but I did what I could. Now I am kindly asking that you review my tests in order to verify their correctness.
Example SUT
In order to demonstrate what I have tried so far and to give you folks something to go on - here is an example SUT.
I am coding a small blog engine. I have a controller by the name of PostsController
whose role is obvious:
public class PostsController : Controller
{
private readonly IPostsRepository repository;
private readonly IMappingEngine mapper;
public PostsController(IPostsRepository repository, IMappingEngine mapper)
{
this.repository = repository;
this.mapper = mapper;
}
public ViewResult Index()
{
var posts = repository.GetAllPublished();
var model =
mapper.Map<IEnumerable<PostViewModel>>(posts);
return View("Index", model);
}
}
In an attempt to keep my controllers thin (and to achieve lose coupling) I introduced a repository by the name of PostsRepository
who implements an interface called IPostsRepository
:
public interface IPostsRepository
{
IEnumerable<Post> All();
IEnumerable<Post> GetAllPublished();
}
public class PostsRepository : IPostsRepository
{
private readonly DatabaseContext context;
public PostsRepository(DatabaseContext context)
{
this.context = context;
}
public IEnumerable<Post> All()
{
return
context.Posts
.OrderBy(post => post.PublishDate);
}
public IEnumerable<Post> GetAllPublished()
{
return
context.Posts
.Where(post => !post.Draft)
.OrderBy(post => post.PublishDate);
}
}
Note that only one of the two methods defined by the repository interface are actually used by the controller in this example. I included the ineffectual method to better reflect a real-world example as that is important to me. I included the IMappingEngine
abstraction (that comes from AutoMapper) for the same reason.
My best efforts at testing this system
Having amalgamated a number of articles and videos I came up with the following unit tests:
[TestFixture]
public class PostsControllerTest
{
private PostsController controller;
private Mock<IPostsRepository> repository;
private Mock<IMappingEngine> mapper;
[SetUp]
public void SetUp()
{
repository = new Mock<IPostsRepository>();
mapper = new Mock<IMappingEngine>();
controller = new PostsController(repository.Object, mapper.Object);
}
[Test]
public void Index_ReturnsCorrectViewName()
{
var actual = controller.Index();
Assert.AreEqual("Index", actual.ViewName);
}
[Test]
public void Index_UsesPostsRepository()
{
controller.Index();
repository.Verify(repo => repo.GetAllPublished(), Times.Once);
}
[Test]
public void Index_ReturnsCorrectModel()
{
var posts = Enumerable.Repeat(new PostViewModel(), 2).ToList();
mapper.Setup(m =>
m.Map<IEnumerable<PostViewModel>>(It.IsAny<IEnumerable<Post>>()))
.Returns(posts);
var actual = controller.Index().Model;
Assert.IsAssignableFrom<List<PostViewModel>>(actual);
}
}
[TestFixture]
public class PostsRepositoryTest
{
private IQueryable<Post> posts;
private Mock<DbSet<Post>> databaseSet;
private Mock<DatabaseContext> databaseContext;
private PostsRepository repository;
public void SetUp()
{
databaseSet = new Mock<DbSet<Post>>();
databaseSet.As<IQueryable<Post>>().Setup(query => query.Provider)
.Returns(posts.Provider);
databaseSet.As<IQueryable<Post>>().Setup(query => query.Expression)
.Returns(posts.Expression);
databaseSet.As<IQueryable<Post>>().Setup(query => query.ElementType)
.Returns(posts.ElementType);
databaseSet.As<IQueryable<Post>>().Setup(query => query.GetEnumrator())
.Returns(posts.GetEnumerator);
databaseContext = new Mock<DatabaseContext>();
databaseContext.Setup(context => context.Posts)
.Returns(databaseSet.Object);
repository = new PostsRepository(databaseContext.Object);
}
[Test]
public void All_ReturnsAll()
{
// arrange
posts = Enumerable.Repeat(new Post(), 2).AsQueryable();
// Because we need to setup the seed data (which varies for each test)
// before mocking the database objects, we cannot use the NUnit SetUp
// attribute and must instead call SetUp manually *after* we setup the
// seed data.
SetUp();
// act
var actual = repository.All();
// assert
Assert.AreEqual(2, actual.Count());
}
[Test]
public void All_OrdersResultsByPublishDate()
{
// arrange
posts = new List<Post>
{
new Post { PublishDate = new DateTime(2014, 1, 3) },
new Post { PublishDate = new DateTime(2014, 1, 2) },
new Post { PublishDate = new DateTime(2014, 1, 1) },
}.AsQueryable();
SetUp();
// act
var actual = repository.All();
// assert
var sorted = posts.OrderBy(post => post.PublishDate);
CollectionAssert.AreEquivalent(actual, sorted);
}
[Test]
public void GetAllPublished_ExcludesDrafts()
{
// arrange
posts = new List<Post>
{
new Post(),
new Post(),
new Post { Draft = true }
}.AsQueryable();
SetUp();
// act
var actual = repository.GetAllPublished();
// assert
Assert.AreEqual(2, actual.Count());
}
[Test]
public void GetAllPublished_OrdersResultsByPublishDate()
{
// arrange
posts = new List<Post>
{
new Post { PublishDate = new DateTime(2014, 1, 3) },
new Post { PublishDate = new DateTime(2014, 1, 2) },
new Post { PublishDate = new DateTime(2014, 1, 1) },
}.AsQueryable();
SetUp();
// act
var actual = repository.GetAllPublished();
// assert
var sorted = posts.OrderBy(post => post.PublishDate);
CollectionAssert.AreEquivalent(actual, sorted);
}
}
Particular areas of concern:
- Starting with the repository - are my repository tests correct?
- Given correct repository tests - are my controller tests correct?
A correct test in my mind is one that is purposeful and thorough. Are my tests correct and thorough?