System.NullReferenceException-.Net(MVC)模拟单元测试

时间:2017-02-15 14:31:03

标签: c# asp.net asp.net-mvc unit-testing moq

我正在使用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());
        }

enter image description here

2 个答案:

答案 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

相关问题