2017-02-15 158 views
0

我正在为我的控制器中的一个方法使用Moq和Nunit框架进行单元测试。我正在努力理解其他对象的嘲笑库的概念,但没有取得太大的成功。System.NullReferenceException - .Net(MVC)模拟单元测试

我有一种方法,不允许用户删除他/她帐户中有未结余额的学生。该方法的逻辑是在我的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

+0

你知道'NullReferenceException'是什么吗?你能调试并找出什么对象是空的? – mason

+0

你能调试并告诉我们你得到的错误是哪一行吗? – Rinktacular

+0

@Rinktacular他已经告诉我们错误来自哪里。 – mason

回答

0

我不知道到底是什么你GetStudentByID方法是干什么的,但似乎它重新变为空。 看看它的代码,检查它是否调用了你没有模拟的方法,或者返回值是否被很好地检索。

希望帮助...:S

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等。这将允许你在你的应用中使用单个控制器,并防止控制器需要知道关于StudentRepositorySchoolContext的任何信息。所有它需要知道的是IStudentRepository。检查this excellent video了。

+0

感谢有关这些信息的资源。由于我很新,因此目前变得非常混乱。对于我的项目,我们有多个控制器,服务,UnitOfWork,Generic Repository ....我通过实现'ID'编辑我的测试代码,而且我仍然得到'NullException'。 – Truecolor

+0

@真彩你有没有做我建议的?你打开MockBehavior.Strict了吗?你嘲笑我建议的方法吗? – mason

+0

我得到模拟行为严格的错误。我在帖子中添加了错误图片。 – Truecolor