我正在开发ASP.net MVC 3.0应用程序。我正在使用MSTest
和Moq
进行单元测试。我已经为我的控制器编写了所有测试方法并运行了那些测试,结果很成功。
现在,我怀疑我是否已经正确地进行了单元测试。因为,我的几乎所有控制器操作都包含数据库调用。
我不嘲笑他们,我只使用Moq嘲笑Session
和Request
个对象。
是否真的有必要模拟数据库调用,因为单元测试意味着测试单个代码单元?我认为带有数据库调用的单元测试控制器违反了上述声明。
如果是这样,任何人都可以解释我如何模拟数据库调用吗?我没有使用任何实体框架。
Updated2:
[httppost]
public void AjaxSave(Model m)
{
m.update(); // Database call
}
答案 0 :(得分:10)
您应该提取使数据库调用成为单独对象的代码(请查看Single Responsibility Principle)。例如。你有控制器
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);
}
}
}
将数据访问代码提取到单独的类(通常称为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;
}
}
}
正如您已经注意到的那样,我声明了抽象,这是由数据访问类实现的:
public interface IPersonRepository
{
List<Person> GetAllPeople();
// other data access API will go here
}
让控制器依赖于这种抽象(这很重要 - 抽象很容易被模拟):
public class PersonController : Controller
{
private IPersonRepository _personRepository;
public PersonController(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public ActionResult Index()
{
var people = _personRepository.GetAllPeople();
return View(people);
}
}
然后将存储库实现注入控制器(Dependency Injection in .NET)并模拟它以进行测试:
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();
答案 1 :(得分:1)
嗯,我认为你有一些设计问题,因为正确的可测试代码永远不会在MVC控制器内部结束数据库代码,你需要更好地实现关注点分离,以便每一段代码都是可单元测试的,这是通过使用一些设计模式来实现,例如服务工厂,依赖注入和控制反转...... Joel Abrahamsson很好地解释了here,以防你不知道我在说什么。您甚至可以查看Castle Windsor,这是一个非常好的开源工具(控制反转)
您仍然可以通过一些努力对控制器进行单元测试,在单元测试中实现设置功能和清理功能。但是,我强烈建议,如果你有一点时间,请修改你的代码,你将不会在很多不必要的依赖项上走得太远。
利奥
答案 2 :(得分:1)
控制器从不应该直接调用数据库(一个 - 不是最重要的 - 原因是这使得控制器几乎无法测试......)。相反,我强烈建议您重构您的代码,以便首先启用可测试性并确保Separation of Concerns:将您的所有数据访问代码放入Repositories中通过接口访问控制器。通过这种方式,您可以轻松地使用Moq模拟它们。