12
\$\begingroup\$

I'm just learning how dependency injection and mocking work, but I'd like some feedback on how I'm setting up a couple of tests. I can get them to pass, but I'm not sure this is all I need.

This is an MVC application that makes Web API calls to return data. For this example I'm running queries in the Web APIs that populate dropdowns.

Please give me any and all suggestions about what I'm doing right or wrong here or anything I should be doing differently.

Setup file for Dependency Injection - Unity.WebAPI (NuGet Package)

UnityConfig.cs

public static class UnityConfig
{
    public static void RegisterComponents()
    {
        var container = new UnityContainer();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        // e.g. container.RegisterType<ITestService, TestService>();

        container.RegisterType<IDropDownDataRepository, DropDownDataRepository>();

        GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
    }
}

Controller

public class DropDownDataController : ApiController
{  
    private IDropDownDataRepository _dropDownDataRepository;

    //Dependency Injection (I'm using Unity.WebAPI)
    public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
    {
        _dropDownDataRepository = dropDownDataRepository;
    }

    [HttpGet]
    public HttpResponseMessage DateList()
    {
        try
        {
            return _dropDownDataRepository.DateList();
        }
        catch
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
    }
}

Repository

public class DropDownDataRepository : IDropDownDataRepository
{
    //Is this fine in here, or should it be injected somehow too?
    private MyDatabaseEntities db = new MyDatabaseEntities();  

    public HttpResponseMessage DateList()
    {
        var sourceQuery = (from p in db.MyProcedure()
                           select p).ToList();

        string result = JsonConvert.SerializeObject(sourceQuery);
        var response = new HttpResponseMessage();
        response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");

        return response;
    }
}

INTERFACE

public interface IDropDownDataRepository
{
    HttpResponseMessage DateList();
}  

Unit tests

/// <summary>
/// Tests the DateList method is run
/// I pieced this kind of test together from examples online
/// I'm assuming this is good for a simple test
/// </summary>
[TestMethod]
public void DateListTest1()
{
    //Arrange
    var mockRepository = new Mock<IDropDownDataRepository>();
    mockRepository.Setup(x => x.DateList());           
    var controller = new DropDownDataController(mockRepository.Object);

    //Act
    controller.DateList();

    //Assert
    mockRepository.VerifyAll();
}



/// <summary>
/// Tests the DateList method returns correct status code.
/// This will run with success, but I'm not sure if that's just
/// because I'm telling it to return what I'm expecting.  
/// I welcome suggestions for improvement.
/// </summary>
[TestMethod]
public void DateListTest2()
{
    //Arrange
    var mockRepository = new Mock<IDropDownDataRepository>();
    mockRepository
        .Setup(x => x.DateList())
        //This will only succeed if I have the Returns property here,
        //but isn't that just bypassing the actual "test" of whether or
        //not this works?
        .Returns(new HttpResponseMessage(HttpStatusCode.OK));

    DropDownDataController controller = new DropDownDataController(mockRepository.Object);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    //Act            
    var response = controller.DateList();

    //Assert
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
\$\endgroup\$
11
\$\begingroup\$

There are a number of issues in your code.

  1. Naming. Methods should use verb phrases to denote the action they perform and try to be specific. So your DateList method should become something like GetWorkingDates, or just GetDates if kind of dates is obvious in your case.

  2. Test naming. Do spend some time on composing a name for your unit test method that describes what exactly is being verified. DateListTest2 does not provide any information except that it deals with DateList() method somehow.

  3. Incorrect responsibilities. Let's look at IDropDownDataRepository interface. It is an interface to obtain a list of dates (probably). And this functionality may be used in different places, not just to render this list in the HTML. But instead of returning IEnumerable<DateEntity> or any other type of collection, it returns HttpResponseMessage, which is only useful for HTTP communication. The only class that knows about specifics of how the list of dates should be used - is your DropDownDataController class. And in your case (no special HTTP response or different HTTP codes) - DropDownDataController.DateList method can return IEnumerable<DateEntity> as well.

  4. Do use AsNoTracking() method when you perform read-only queries.

  5. Test expected resulting behavior instead of the way it was done. If your class manages to satisfy all your tests without calling the mocked instance, then either result of the mocked instance call is insignificant, or you should improve tests by adding new test(s) to verify the change in behavior depending on outcome of the call to mocked instance. Your DateListTest1 test currently expose such a problem, as the only thing that it verifies is that controller calls IDropDownDataRepository.DateList() to render the output. Instead, you should setup IDropDownDataRepository.DateList() to return some list, and verify that the return value of the controller method returns the data in expected way. If the class under test does not call the mocked instance - it will fail such a verification anyway. In general the usage of Mock<T>.VerifyAll is not recommended.

As a result you may get something like this:

public interface IDropDownDataRepository
{
    IEnumerable<DateEntity> GetDates();
}

public class DropDownDataRepository : IDropDownDataRepository
{
    //Should be injected as well
    private MyDatabaseEntities db = new MyDatabaseEntities();

    public IEnumerable<DateEntity> GetDates()
    {
        return from date in db.MyProcedure().AsNoTracking()
               select date;
    }
}

public class DropDownDataController : ApiController
{
    private IDropDownDataRepository _dropDownDataRepository;

    public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
    {
        _dropDownDataRepository = dropDownDataRepository;
    }

    [HttpGet]
    public IEnumerable<DateEntity> GetDates()
    {
        try
        {
            return _dropDownDataRepository.GetDates();
        }
        catch
        {
            // I would leave the default error handling which would return 500 error
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
    }
}

public class DropDownDataControllerTest
{
    [TestMethod]
    public void GetDatesShouldReturnDatesFromRepository()
    {
        //Arrange
        var mockRepository = new Mock<IDropDownDataRepository>();
        var dateEntities = new[] { new DateEntity { Date = new DateTime(1990, 1, 1) }, new DateEntity { Date = new DateTime(1990, 1, 1) } };
        mockRepository
            .Setup(x => x.GetDates())
            .Returns(dateEntities);

        DropDownDataController controller = new DropDownDataController(mockRepository.Object);

        //Act            
        var response = controller.GetDates();

        //Assert
        Assert.AreEqual(dateEntities, response);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.