HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Repository and Controller tests

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
andtestsrepositorycontroller

Problem

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>(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 All();
    IEnumerable GetAllPublished();
}

public class PostsRepository : IPostsRepository
{
    private readonly DatabaseContext context;

    public PostsRepository(DatabaseContext context)
    {
        this.context = context;
    }

    public IEnumerable All()
    {
        return 
            context.Posts
                   .OrderBy(post => post.PublishDate);
    }

    public IEnumerable 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 examp

Solution

As Phil Sandler said, the code here is so thin that it's possibly questionable how much value unit tests actually add. However, I would say that their two main benefits are both still relevant:

  • Living documentation of what a unit of code should do



  • Instant feedback for future refactorings



While it's much more likely that you'll add to PostsRepository rather than modifying either of those methods, even simple controller methods may be refactored. For example, it's quite likely that you'll want to add paging, which will mean refactoring Index. Unit tests could be valuable there.
Mock commands, not queries

You may have heard of the idea of "command-query separation" (CQS). To quote wikipedia:

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

Putting aside whether you adhere to CQS, or to what extent, the idea of everything being either a command or a query is a useful one in guiding how you test it.

For a pure query, a necessary and sufficient condition for its correctness is the correctness of what it returns. It really doesn't matter what it does, which means that a test shouldn't be concerned with its implementation. A test that relies on implementation details is coupling itself to the way the query method works in a way that violates encapsulation. This adds extra maintainance effort if you ever want to change the implementation. It also undermines both of the purposes of unit tests I mentioned at the top. They're not good living documentation if they test details which are not actually aspects of expected behaviour to an external consumer, and they can't give useful instant feedback if they have to be updated alongside the SUT.

So, all that said, take a look at Index_UsesPostsRepository. What this is doing is taking a pure query method, and instead of checking the result it returns, it's looking at the specific implementation of how it gets that result. Consider again the possible future requirement I mentioned before, of adding paging. For efficiency, you might add a new method to the repository which takes the page number and entries per page as parameters (so that you can do the paging in generated SQL, rather than in memory), and switch to using that in Index instead. Now, even though Index is still perfectly valid, your test will fail, and will need rewriting. That's because your test is testing an implementation detail rather than actually documenting what the method should do.

The smell here is that you're calling a Verify method on a mock when testing a pure query method. It's fine to use a mock object as a stub or provider of fake data for a method like this, but you should be very wary about any Verify calls.

You don't have any commands yet, but when you do, that will be where using a mock as a full mock, including Verify, will come in useful. For a system like this, a command will often be something that modifies the persisted state, like adding a new blog post. Say that's done by calling an Add method on the repository. Calling that Add method, and passing it the correct parameters, will be the intended behaviour of the method, rather than just a particular implementation, so in that case, verifying that it's called correctly will be just what you want in a test. In this case, it will accurately be documenting what the method should do, and it never break during a refactoring unless you actually introduce a bug, which is exactly what you want.
Dealing with DbSet and DbContext

Unfortunately, this is a bit messy. There's essentially three things you might want:

  • DbSet for querying, through the IQueryable interface



  • DbSet for adding items (Add), through the IDbSet interface



  • DbContext for saving changes (SaveChanges), not on any interface



This isn't very easy to do neatly. As you've seen, if you pass the DbContext into your repository, then mocking the DbSet just for querying is a bit nasty, and you'll run into further problems if you want to mock the context to check if SaveChanges is called.

I'm not fully up on how people tend to deal with this, but one strategy might be to define an interface like:

public interface IDataAccess
{
    IQueryable AsQueryable();
    void Add(TEntity entity);
    int SaveChanges();
}


You may be able to come up with a better name than IDataAccess. The default implementation would be very simple, with AsQueryable just returning the appropriate DbSet, and Add and SaveChanges passing through to the corresponding methods on the DbSet and DbContext respectively. Then the repository would never touch the DbSet or DbContext directly and use this instead. Because this would be so thin, and should pretty much never change, it wouldn't need its own testing, and would be straightforward to mock. For the tests so far, you'd just set up your mock to return posts.AsQueryable() for the `AsQueryable(

Code Snippets

public interface IDataAccess<TEntity>
{
    IQueryable<TEntity> AsQueryable();
    void Add(TEntity entity);
    int SaveChanges();
}
public class PostBuilder
{
    private DateTime _publishDate;

    public PostBuilder()
    {
        _publishDate = DateTime.Now;
    }

    public PostBuilder WithPublishDate(DateTime publishDate)
    {
        _publishDate = publishDate;
        return this;
    }

    public Post Build()
    {
        return new Post { PublishDate = _publishDate };
    }
}

Context

StackExchange Code Review Q#59537, answer score: 10

Revisions (0)

No revisions yet.