模拟非虚拟发布的模型属性和方法

时间:2018-02-20 05:37:21

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

我将人员模型类定义为:

public class PersonModel
{
    public bool SelectionSubmitted = false;

    public bool ShowValidationSummary = false;

    public string Name;

    public string Get()
    {            
        //actual implementation return some value from the db
        return string.Empty;
    }
}

控制器实现如下:

class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index(PersonModel model)
    {
        if (model.SelectionSubmitted && !ValidateSelections(model))
        {
            model.ShowValidationSummary = true;
        }

        return View("Index", model.Get());
    }

    private bool ValidateSelections(PersonModel model)
    {
        if(model.Name == "")
        {
            ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
        }
        return ModelState.IsValid;
    }       
}

测试类和方法定义为:

[TestClass]
public class ChildWithoutPlacementControllerTest
{
    private readonly Mock<PersonModel> _mockPersonModel;

    public ChildWithoutPlacementControllerTest()
    {
        _mockPersonModel = new Mock<PersonModel>();
    }

    [TestMethod]
    public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown()
    {
        //Arrange
        HomeController controller = new HomeController();
        _mockPersonModel.Setup(x => x.Get()).Returns(It.IsAny<string>());
        _mockPersonModel.SetupGet(x => x.Name).Returns(string.Empty);
        _mockPersonModel.SetupGet(x => x.SelectionSubmitted).Returns(true);

        //Act
        controller.Index(_mockPersonModel.Object);

        //Assert
        var isShowSummarySetToTrue = _mockPersonModel.Object.ShowValidationSummary;
        Assert.IsTrue(isShowSummarySetToTrue);
    }
}

我想要达到的目的是分别将SelectionSubmittedName属性模仿到truestring.Empty以及Setup Get方法PersonModel类,并检查测试返回对象的ShowValidationSummary是否设置为true

但是,我得到的是我无法设置非虚拟属性Name。

我做错了什么或者有没有办法在不改变实施代码的情况下做到这一点?

1 个答案:

答案 0 :(得分:1)

  

我做错了什么

这似乎是XY problem

  

有没有办法在不改变实现代码的情况下做到这一点

在这种情况下,确实没有必要使用moq。您可以使用继承来制作要在测试中使用的假模型。假模型将覆盖与数据库紧密耦合的方法。 (稍后会详细介绍)

public class FakePerson : PersonModel {
    public new string Get() {
        return string.Empty; //Not calling the base Get
    }
}

然后可以重构测试以使用假模型并按预期行使完成。

[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
    //Arrange
    var fakePersonModel = new FakePerson() {
        Name = string.Empty,
        SelectionSubmitted = true
    };
    var controller = new HomeController();

    //Act
    controller.Index(fakePersonModel);

    //Assert
    var isShowSummarySetToTrue = fakePersonModel.ShowValidationSummary;
    Assert.IsTrue(isShowSummarySetToTrue);
}

除此之外,如果实际的Get实现按照您在此处所述的方式执行,那么您的模型似乎做得很多

  

实际实现从db

返回一些值

考虑将该功能重构为服务(单一责任原则/关注点分离)

public interface IPersonModelService {
    string Get(PersonModel person);
}

public class PersonModelService : IPersonModelService {

    public string Get(PersonModel person) {
        //actual implementation return some value from the db
    }
}

并尽可能保持模型的精益。还可以考虑将这些公共字段重构为公共属性。

public class PersonModel {
    public bool SelectionSubmitted { get; set; }

    public bool ShowValidationSummary { get; set; }

    public string Name { get; set; }

}

控制器将依赖于服务抽象

class HomeController : Controller {
    private IPersonModelService service;

    public HomeController(IPersonModelService service) {
        this.service = service;
    }

    [HttpGet]
    public ActionResult Index(PersonModel model) {
        if (model.SelectionSubmitted && !ValidateSelections(model)) {
            model.ShowValidationSummary = true;
        }

        return View("Index", service.Get(model));
    }

    private bool ValidateSelections(PersonModel model) {
        if (model.Name == "") {
            ModelState.AddModelError("EmptyPersonName", "Person name cannot be null");
        }
        return ModelState.IsValid;
    }
}

现在可以单独完成测试。

[TestMethod]
public void GivenPerson_WhenSearchingForFutureBirthDate_ThenValidationMessageShouldBeShown() {
    //Arrange
    var model = new PersonModel() {
        Name = string.Empty,
        SelectionSubmitted = true
    };

    var serviceMock = new Mock<IPersonModelService>();
    serviceMock.Setup(_ => _.Get(It.IsAny<PersonModel>())).Returns(string.Empty);
    var controller = new HomeController(serviceMock.Object);

    //Act
    controller.Index(model);

    //Assert
    var isShowSummarySetToTrue = model.ShowValidationSummary;
    Assert.IsTrue(isShowSummarySetToTrue);
}