How to Properly Test Controllers in ASP.net MVC that has database calls

前端 未结 3 1018
孤城傲影
孤城傲影 2020-12-31 20:52

I am working on an ASP.net MVC 3.0 Application. I am using MSTest along with Moq for unit testing. I have written all the test methods for my contr

相关标签:
3条回答
  • 2020-12-31 21:08

    A controller never should call the database directly (one - not the most important - reason for that is that this makes the controller almost impossible to test...). Instead, I strongly advise you to refactor your code to enable testability in the first place and also to have proper Separation of Concerns: Put all your data access code into Repositories which you then access in your controllers via interfaces. This way, you can easiliy mock them with Moq.

    0 讨论(0)
  • 2020-12-31 21:15

    You should extract code which makes database calls into separate object (take a look on Single Responsibility Principle). E.g. you have controller

    public class PersonController : Controller
    {
         public ActionResult Index()
         { 
             var connectionString = 
                 ConfigurationManager.ConnectionStrings["foo"].ConnectionString;
             using(var connection = new SqlConnection(connectionString))
             {
                 string sql = "SELECT Name FROM People";
                 var command = connection.CreateCommand(sql);
                 var reader = command.ExecuteReader();
                 List<Person> people = new List<Person>();
                 while(reader.Read())
                 {
                     Person p = new Person();
                     p.Name = reader["Name"].ToString();
                     people.Add(p);
                 }
    
                 return View(people);
             }
         }
    }
    

    Extract data-access code into separate class (usually such classes called repositories):

    public class PersonRepository : IPersonRepository
    {
         public List<Person> GetAllPeople()
         {
             var connectionString = 
                 ConfigurationManager.ConnectionStrings["foo"].ConnectionString;
             using(var connection = new SqlConnection(connectionString))
             {
                 string sql = "SELECT Name FROM People";
                 var command = connection.CreateCommand(sql);
                 var reader = command.ExecuteReader();
                 List<Person> people = new List<Person>();
                 while(reader.Read())
                 {
                     Person p = new Person();
                     p.Name = reader["Name"].ToString();
                     people.Add(p);
                 }
    
                 return people;
             }
         }
    }
    

    As you already notices I declared abstraction which is implemented by data-access class:

    public interface IPersonRepository
    {
        List<Person> GetAllPeople();
        // other data access API will go here
    }
    

    Make controller depend on this abstraction (it's important - abstraction is easy to mock):

    public class PersonController : Controller
    {
         private IPersonRepository _personRepository;
    
         public PersonController(IPersonRepository personRepository)
         {
             _personRepository = personRepository;
         }
    
         public ActionResult Index()
         { 
             var people = _personRepository.GetAllPeople();
             return View(people);             
         }
    }
    

    Then inject repository implementation into controller (Dependency Injection in .NET) and mock it for tests:

    var repositoryMock = new Mock<IPersonRepository>();
    var people = new List<People>(); // provide some sample list 
    repositoryMock.Setup(r => r.GetAllPeople()).Return(people);
    var controller = new PersonController(repositoryMock.Object);
    
    var result = (ViewResult)controller.Index();
    // Assert here
    Assert.AreEqual(result.ViewName, "Index");
    Assert.AreEqual(result.Model, people);
    repositoryMock.VerifyAll();
    
    0 讨论(0)
  • 2020-12-31 21:19

    Well, I think you have some design issues here because proper testable code will never end up with database code inside an MVC Controller, you need to better implement separation of concerns so that each piece of code is unit testable, and this is achieve by using some design patterns such as Service Factory, Dependency Injection and Inversion of Control...Joel Abrahamsson explains it pretty well here in case your not aware of what I'm talking about. You can even check out Castle Windsor, a pretty good open source tool for this purpose (Inversion of Control)

    You can still unit test you controllers with a bit of effort, implementing set-up functions and clean-up functions in your unit tests. But, I strongly recommend, if you have a bit of time, REFACTOR your code, you won't get very far with some many unnecessary dependencies.

    Leo

    0 讨论(0)
提交回复
热议问题