【问题标题】:Testing a MVC Controller fails with NULL reference exception测试 MVC 控制器失败并出现 NULL 引用异常
【发布时间】:2015-08-27 08:47:26
【问题描述】:

下面是我要测试的设置。

控制器:

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() 尝试从数据库中获取一个对象并返回一个空对象,因为没有学生使用从测试方法传递的 id。 然后测试因空引用异常而失败。

现在被测系统不应该访问数据库。我不知道为什么会这样!

编写此测试的任何其他人也将尝试传递一个虚拟对象,该对象肯定会在 GetStudentInfo() 处失败,就像现在设置测试的方式一样。

我该怎么做才能完成这项工作?

【问题讨论】:

  • 如果没有具有该 ID 的学生,那么我会说您的测试已成功识别出错误。您需要处理existingStudent 为空的情况。如果代码被破坏,那么编辑测试以使其正常工作是没有意义的。
  • 在实际使用中这永远不会发生,因为总会有一个学生。
  • 你想模拟上下文吗?
  • 这是正确的代码吗?您有_controller.UpsertDoc(),但没有显示任何具有该名称的方法。您有 .Setup(x => x.GetStudentRule).Setup(x => x.ReturnStudentRule) 但没有显示这些方法
  • 编辑了通话。另外两个只是从一个对象到另一个对象的映射器,没有任何业务逻辑。

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


【解决方案1】:

我不确定我是否正确理解了您的问题,但是查看提供的代码 sn-ps,测试将进入数据库,因为未定义模拟对象及其期望。

我会像这样实施解决方案 -

我假设您的_updateStudentManager 对象适用于为Student 进行数据库交互的类。我称之为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 方法时,它不会访问数据库,而是返回模拟对象中设置的值。

这只是一个高级实现,当然您可以根据您的设计对其进行修改。希望对你有帮助

【讨论】:

    猜你喜欢
    • 2013-08-11
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多