Kunal Khivensara
Kunal Khivensara

Reputation: 1669

Mocking non virtual posted model properties and method

I have person model class defined as:

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

The controller implementation is as follows:

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

The test class and method is defined as:

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

What I want to achieve is mock the SelectionSubmitted and Name property to true and string.Empty respectively also Setup the Get method of PersonModel class, and check if the test return object has ShowValidationSummary set to true.

However, I am getting that I can't set up the non-virtual property Name.

Am I doing something wrong or is there any way to do it without changing the implementation code?

Upvotes: 3

Views: 2254

Answers (1)

Nkosi
Nkosi

Reputation: 247018

Am I doing something wrong

This appears to be an XY problem.

is there any way to do it without changing the implementation code

There really is no need for moq in this scenario. You can use inheritance to craft a fake model to be used in the test. The fake model will override the method that is tightly coupled to the database. (more on that later)

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

The test can then be refactored to use the fake model and be exercised to completion as intended.

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

That aside, your model appears to be doing to much if the actual Get implementation does as you stated here

actual implementation return some value from the db

Consider refactoring that functionality out into a service (Single Responsibility Principle / Separation of Concerns)

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

public class PersonModelService : IPersonModelService {

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

and keep the model as lean as possible. Also consider refactoring those public fields into public properties.

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

    public bool ShowValidationSummary { get; set; }

    public string Name { get; set; }

}

The controller would depend on the service abstraction

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

And now the test can be exercised to completion in isolation.

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

Upvotes: 1

Related Questions