我正在使用Moq和Nunit Framework对我的Controller中的一个方法进行单元测试。我正在努力理解Mocking存储库的概念。其他对象,但没有取得多大成功。
我有一种方法,不允许用户删除在他/她的帐户中有未结余额的学生。该方法的逻辑在我的StudentController中,在POST方法中,我也使用存储库和依赖注入(不确定是否导致问题)。当我运行我的单元测试时,有时会转到我的GET Delete()
方法,如果它转到POST method
我得到一个错误,说“对象引用未设置为对象的实例”代码说这个if (s.PaymentDue > 0)
?
StudentController
public class StudentController : Controller
{
private IStudentRepository studentRepository;
public StudentController()
{
this.studentRepository = new StudentRepository(new SchoolContext());
}
public StudentController(IStudentRepository studentRepository)
{
this.studentRepository = studentRepository;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id)
{
//studentRepository.DeleteStudent(id);
Student s = studentRepository.GetStudentByID(id);
var paymentDue = false;
if (s.PaymentDue > 0)
{
paymentDue = true;
ViewBag.ErrorMessage = "Cannot delete student. Student has overdue payment. Need to CLEAR payment before deletion!";
return View(s);
}
if (!paymentDue)
{
try
{
Student student = studentRepository.GetStudentByID(id);
studentRepository.DeleteStudent(id);
studentRepository.Save();
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException and add a line here to write a log.
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}
//return View(s);
return RedirectToAction("Index");
}
单元测试方法
private int studentID;
[TestMethod]
public void StudentDeleteTest()
{
//create list of Students to return
var listOfStudents = new List<Student>();
listOfStudents.Add(new Student
{
LastName = "Abc",
FirstMidName = "Abcd",
EnrollmentDate = Convert.ToDateTime("11/23/2010"),
PaymentDue = 20
});
Mock<IStudentRepository> mockStudentRepository = new Mock<IStudentRepository>();
mockStudentRepository.Setup(x => x.GetStudents()).Returns(listOfStudents);
var student = new StudentController(mockStudentRepository.Object);
//Act
student.Delete(studentID);
////Assert
mockStudentRepository.Verify(x => x.DeleteStudent(studentID), Times.AtLeastOnce());
}
答案 0 :(得分:5)
你没有嘲笑GetStudentByID
。你只是嘲笑GetStudents
(你甚至没有通过你正在测试的动作方法调用它)。调用未模拟的方法时Moq的默认行为是返回null。因此,当控制器调用studentRepository.GetStudentByID
时,它返回null。然后,当您尝试访问学生的PaymentDue
属性时,它为空,从而导致NullReferenceException
。
要解决的两件事:嘲笑方法并启用MockBehavior.Strict
。
var mockStudentRepository = new Mock<IStudentRepository>(MockBehaviorStrict);
当您尝试在尚未模拟的存储库上调用方法而不是返回null时,这将导致发生异常。这使您可以快速失败并轻松找到尚未模拟的内容。
然后为该方法添加模拟:
var student = new Student
{
Id = 9974,
LastName = "Abc",
FirstMidName = "Abcd",
EnrollmentDate = Convert.ToDateTime("11/23/2010"),
PaymentDue = 20
};
mockStudentRepository.Setup(x =>
x.GetStudentByID(student.Id))
.Returns(student);
我没有检查你的其余代码,看你是不是在嘲笑其他任何东西,但启用严格的模拟行为将帮助你找到你需要模拟的东西。
...好吧我确实检查过了。您还需要模拟存储库的Save
方法。
另外,您的控制器正在调用studentRepository.GetStudentByID(id)
两次。这将导致对您的存储库(可能还有数据库)的不必要调用,并减慢速度。相反,只需重复使用已包含学生的s
。
另一方面,您似乎没有在控制器中使用依赖项注入框架。我建议您查看AutoFac(我最喜欢的),Ninject,Unity等..这样您就可以在应用中使用单个控制器,并防止控制器需要了解{{1 }或StudentRepository
。所有它需要知道的是SchoolContext
。检查this excellent video。
答案 1 :(得分:0)
我不确切知道你的 GetStudentByID 方法正在做什么,但似乎它返回null。 查看其代码,检查它是否正在调用您没有模拟的方法,或者是否检索到了返回值。
希望有帮助......:S