测试 MVC 控制器失败,出现 NULL 引用异常
Testing a MVC Controller fails with NULL reference exception
下面是我要测试的设置。
控制器:
public ActionResult UpsertStudent(StudentModel studentModel)
{
try
{
if (!CheckStudentUpdateForEdit(studentModel))
{
return Json(new { result = STUDENT_EXISTS });
}
// remaining code removed for brevity
}
private bool CheckStudentUpdateForEdit(StudentModel studentModel)
{
var returnVal = true;
var existingStudent = _updateStudentManager.GetStudentInfo(studentModel.Id);
if (existingStudent.StudentType == "Day Scholar")
{
returnVal = true;
}
else
{
returnVal = false;
}
return returnVal;
}
测试方法:
public void AllowStudentUpdates_Success()
{
var studentModel = new StudentModel()
{
StudentName = "Joe",
Id = "123",
StudentType = "Day Scholar"
};
var studentToAdd = new Student()
{
Id = "123",
Name = "Joe",
StartDate = DateTime.UtcNow.ToShortDateString(),
StudentType = "Day Scholar",
EndDate = "08/10/2016"
};
_studentRulesHelper.Setup(x => x.GetStudentRule(studentModel, true)).Returns(studentToAdd);
_productRulesHelper.Setup(x => x.ReturnStudentRule(studentModel, true)).Returns(studentToAdd);
var res = _controller.UpsertStudent(studentModel) as JsonResult;
if (res != null) Assert.AreEqual("{ result = True }", res.Data.ToString());
}
当它遇到 UpsertDoc
调用时,它转到控制器中的实际调用并尝试执行 CheckStudentUpdateForEdit()
GetStudentInfo()
尝试从数据库中获取对象和 returns 一个空对象,因为没有学生具有从测试方法传递的 id。
然后测试失败并出现 Null Reference 异常。
现在被测系统不应该访问数据库。我不知道为什么这是另一种方式!
编写此测试的任何其他人也会尝试传递一个虚拟对象,该对象在GetStudentInfo()
测试现在设置的方式下肯定会失败。
我该怎么做才能完成这项工作?
我不确定我是否正确理解了你的问题,但查看提供的代码片段,测试将进入数据库,因为未定义模拟对象及其期望。
我会像这样实施解决方案 -
我假设您的 _updateStudentManager
对象是针对正在为 Student
进行数据库交互的 class。我称它为 StudentRepository
。为了让您能够模拟行为,我会让它成为 Interface
驱动。
所以通常我的设置看起来像这样 -
//Interface
public interface IStudentrepository
{
StudentModel GetStudentInfo(int studentId);
}
//Class implementing IStudentrepository
public class StudentRepository : IStudentrepository
{
public StudentModel GetStudentInfo(int studentId)
{
//Implementation goes here
}
}
现在在我的控制器中,我会有一个 IStudentrepository 的实例,它可以通过构造函数注入。
public class StudentController
{
private readonly IStudentrepository updateStudentManager;
public StudentController(IStudentrepository updateStudentManager)
{
this.updateStudentManager = updateStudentManager;
}
}
//Rest of the code for controller....
现在在编写我的 Test
时,我将创建一个 IStudentrepository
的模拟对象,定义对模拟对象的期望,并在创建控制器对象时注入它。像这样的东西。
[TestMethod]
public void TestMethod1()
{
//--Arrange--
//Define a mock object for student repository
var mock = new Mock<IStudentrepository>();
//Define the expectations of the mock object
mock.Setup(s => s.GetStudentInfo(It.IsAny<int>()))
.Returns(new StudentModel {/*return the required object */ });
//Instantiate controller and inject the mock object
StudentController _controller = new StudentController(mock.Object);
//--Act--
var res = _controller.UpsertStudent(studentModel) as JsonResult;
//--Assert--
if (res != null) Assert.AreEqual("{ result = True }", res.Data.ToString());
}
现在,当您的测试方法调用 GetStudentInfo
方法时,它不会访问数据库,而是 return 模拟对象中设置的值。
这只是一个高级实现,当然您可以根据自己的设计进行修改。希望对你有帮助
下面是我要测试的设置。
控制器:
public ActionResult UpsertStudent(StudentModel studentModel)
{
try
{
if (!CheckStudentUpdateForEdit(studentModel))
{
return Json(new { result = STUDENT_EXISTS });
}
// remaining code removed for brevity
}
private bool CheckStudentUpdateForEdit(StudentModel studentModel)
{
var returnVal = true;
var existingStudent = _updateStudentManager.GetStudentInfo(studentModel.Id);
if (existingStudent.StudentType == "Day Scholar")
{
returnVal = true;
}
else
{
returnVal = false;
}
return returnVal;
}
测试方法:
public void AllowStudentUpdates_Success()
{
var studentModel = new StudentModel()
{
StudentName = "Joe",
Id = "123",
StudentType = "Day Scholar"
};
var studentToAdd = new Student()
{
Id = "123",
Name = "Joe",
StartDate = DateTime.UtcNow.ToShortDateString(),
StudentType = "Day Scholar",
EndDate = "08/10/2016"
};
_studentRulesHelper.Setup(x => x.GetStudentRule(studentModel, true)).Returns(studentToAdd);
_productRulesHelper.Setup(x => x.ReturnStudentRule(studentModel, true)).Returns(studentToAdd);
var res = _controller.UpsertStudent(studentModel) as JsonResult;
if (res != null) Assert.AreEqual("{ result = True }", res.Data.ToString());
}
当它遇到 UpsertDoc
调用时,它转到控制器中的实际调用并尝试执行 CheckStudentUpdateForEdit()
GetStudentInfo()
尝试从数据库中获取对象和 returns 一个空对象,因为没有学生具有从测试方法传递的 id。
然后测试失败并出现 Null Reference 异常。
现在被测系统不应该访问数据库。我不知道为什么这是另一种方式!
编写此测试的任何其他人也会尝试传递一个虚拟对象,该对象在GetStudentInfo()
测试现在设置的方式下肯定会失败。
我该怎么做才能完成这项工作?
我不确定我是否正确理解了你的问题,但查看提供的代码片段,测试将进入数据库,因为未定义模拟对象及其期望。
我会像这样实施解决方案 -
我假设您的 _updateStudentManager
对象是针对正在为 Student
进行数据库交互的 class。我称它为 StudentRepository
。为了让您能够模拟行为,我会让它成为 Interface
驱动。
所以通常我的设置看起来像这样 -
//Interface
public interface IStudentrepository
{
StudentModel GetStudentInfo(int studentId);
}
//Class implementing IStudentrepository
public class StudentRepository : IStudentrepository
{
public StudentModel GetStudentInfo(int studentId)
{
//Implementation goes here
}
}
现在在我的控制器中,我会有一个 IStudentrepository 的实例,它可以通过构造函数注入。
public class StudentController
{
private readonly IStudentrepository updateStudentManager;
public StudentController(IStudentrepository updateStudentManager)
{
this.updateStudentManager = updateStudentManager;
}
}
//Rest of the code for controller....
现在在编写我的 Test
时,我将创建一个 IStudentrepository
的模拟对象,定义对模拟对象的期望,并在创建控制器对象时注入它。像这样的东西。
[TestMethod]
public void TestMethod1()
{
//--Arrange--
//Define a mock object for student repository
var mock = new Mock<IStudentrepository>();
//Define the expectations of the mock object
mock.Setup(s => s.GetStudentInfo(It.IsAny<int>()))
.Returns(new StudentModel {/*return the required object */ });
//Instantiate controller and inject the mock object
StudentController _controller = new StudentController(mock.Object);
//--Act--
var res = _controller.UpsertStudent(studentModel) as JsonResult;
//--Assert--
if (res != null) Assert.AreEqual("{ result = True }", res.Data.ToString());
}
现在,当您的测试方法调用 GetStudentInfo
方法时,它不会访问数据库,而是 return 模拟对象中设置的值。
这只是一个高级实现,当然您可以根据自己的设计进行修改。希望对你有帮助