我一直在研究NuGetGallery中单元测试的完成方式。我观察到当控制器被测试时,服务类被模拟了。这对我来说很有意义,因为在测试控制器逻辑时,我不想担心下面的架构层。在使用这种方法一段时间后,我注意到当我的服务类发生变化时,我经常在我的控制器测试中修复我的模拟。为了解决这个问题,在没有咨询比我聪明的人的情况下,我开始编写这样的测试(别担心,我还没有做到这一点):
public class PersonController : Controller
{
private readonly LESRepository _repository;
public PersonController(LESRepository repository)
{
_repository = repository;
}
public ActionResult Index(int id)
{
var model = _repository.GetAll<Person>()
.FirstOrDefault(x => x.Id == id);
var viewModel = new VMPerson(model);
return View(viewModel);
}
}
public class PersonControllerTests
{
public void can_get_person()
{
var person = _helper.CreatePerson(username: "John");
var controller = new PersonController(_repository);
controller.FakeOutContext();
var result = (ViewResult)controller.Index(person.Id);
var model = (VMPerson)result.Model;
Assert.IsTrue(model.Person.Username == "John");
}
}
我想这将是集成测试,因为我使用的是真实数据库(我更喜欢内存数据库)。我通过将数据放入我的数据库来开始我的测试(每个测试都在一个事务中运行,并在测试完成时回滚)。然后我打电话给我的控制器,我真的不在乎它如何从数据库中检索数据(通过存储库或服务类),只是要发送到视图的模型必须具有我放入数据库的记录,也就是我的断言. 这种方法很酷的一点是,很多时候我可以继续添加更多的复杂层,而无需更改我的控制器测试:
public class PersonController : Controller
{
private readonly LESRepository _repository;
private readonly PersonService _personService;
public PersonController(LESRepository repository)
{
_repository = repository;
_personService = new PersonService(_repository);
}
public ActionResult Index(int id)
{
var model = _personService.GetActivePerson(id);
if(model == null)
return PersonNotFoundResult();
var viewModel = new VMPerson(model);
return View(viewModel);
}
}
现在我意识到我没有为我的 PersonService 创建一个接口并将它传递给我的控制器的构造函数。原因是 1)我不打算模拟我的 PersonService 和 2)我觉得我不需要注入我的依赖项,因为我的 PersonController 现在只需要依赖一种类型的 PersonService。
我是单元测试的新手,我总是很高兴被证明我错了。请指出为什么我测试控制器的方式可能是一个非常糟糕的主意(除了我的测试运行时间明显增加)。