【问题标题】:System.NullReferenceException-.Net (MVC) Mock Unit TestSystem.NullReferenceException-.Net (MVC) 模拟单元测试
【发布时间】:2017-02-15 14:31:03
【问题描述】:

我正在使用 Moq 和 Nunit 框架对我的控制器中的一种方法进行单元测试。我正在努力理解模拟存储库和其他对象的概念,但没有取得多大成功。

我有一种方法不允许用户删除其帐户中有未结余额的学生。该方法的逻辑在我的 StudentController 中,在 POST 方法中,并且我还在使用存储库和依赖注入(不确定这是否会导致问题)。当我运行我的单元测试时,有时它会转到我的GET Delete() 方法,如果它转到POST method,我会收到一条错误消息,指出“对象引用未设置为对象的实例”,因为代码行说这个@ 987654325@?

学生控制器

 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());
        }

【问题讨论】:

  • 你知道NullReferenceException 是什么吗?你能调试并找出什么对象是空的吗?
  • 您能否调试并告诉我们您在哪一行得到错误?
  • @Rinktacular 他已经告诉我们错误来自哪一行。
  • s.PaymentDue 是可以为空的类型吗?是否必须指定 s.PaymentDue.Value?
  • 你没有嘲笑GetStudentByID。你只是嘲笑GetStudents

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


【解决方案1】:

你没有嘲笑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 等。这将允许您在应用程序中使用单个控制器并防止控制器需要了解有关 StudentRepositorySchoolContext 的任何信息.它只需要知道IStudentRepository。签出this excellent video

【讨论】:

  • 感谢提供信息的资源。目前它变得非常混乱,因为我对此很陌生。对于我的项目,我们有多个控制器、服务、UnitOfWork、通用存储库......我通过实现ID 编辑了我的测试代码,我仍然得到NullException
  • @Truecolor 你按照我的建议做了吗?你打开 MockBehavior.Strict 了吗?你嘲笑我建议的方法了吗?
  • 我收到模拟行为严格错误。我在帖子中添加了错误图片。
  • @Truecolor 我看了看图片。应该很清楚。您传递的学生 ID 为 0。在我的示例中,模拟传递了您为 student 定义的任何 ID 的 ID。我建议你删除你的 studentID 变量,只使用 student.ID 所以你只将它设置在一个地方。请仔细阅读我的回答,里面有很多内容,了解所有内容很重要。
【解决方案2】:

我不确切知道您的 GetStudentByID 方法在做什么,但它似乎返回 null。 查看它的代码,检查它是否调用了你没有模拟的方法,或者返回值是否被很好地检索。

希望对您有所帮助...:S

【讨论】:

    猜你喜欢
    • 2017-04-14
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多