User_18
User_18

Reputation: 59

Unit test fails when ModelState is valid

I wrote a unit test

// Arrange                    
FocusController controller = new FocusController();
controller.ModelState.AddModelError("", "mock error message");
// Act

FocusFormModel focus = new FocusFormModel();
focus.FocusName = "Mock Focus";
focus.Description = "Mock Description";
focus.GroupId = 1;
var result = controller.CreateFocus(focus) as RedirectToRouteResult;
//// Assert 

Assert.That(result, Is.Not.Null);

here the unit test fails as model state is not valid.

My controller action is:

[HttpPost]
public ActionResult CreateFocus(FocusFormModel focus)
{ 
    if (ModelState.IsValid)
    {
        var createdfocus = focusService.GetFocus(focus.FocusName);
        return RedirectToAction("Focus", new { id = createdfocus.FocusId });
    }

    return View("CreateFocus",focus);
}

My Index action:

Public ActionResult Index(int id)
{
    return View();
}

Upvotes: 1

Views: 1725

Answers (2)

Felipe Oriani
Felipe Oriani

Reputation: 38608

Yes, it will fail because you have an error on the ModelState, so you could compare the result type of your action method, check the modelState and check the model as well. I recommend test doing a code like this:

[TestMethod]
public void Should_have_return_an_error()
{
    FocusController controller = new FocusController();

    // you add this value on ModelState to force the error
    controller.ModelState.AddModelError("", "mock error message");

    // Act
    FocusFormModel focus = new FocusFormModel();
    focus.FocusName = "Mock Focus";
    focus.Description = "Mock Description";
    focus.GroupId = 1;

    const string viewNameResult = "Index"; //or whatever your action tested should return

    // it will return the error
    var result = controller.CreateFocus(focus) as ViewResult;


    //// Assert the Action type result...
    Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

    //// Assert the model..
    Assert.AreEqual(focus, result.Model);

    //// Aseert the ModelState
    Assert.IsFalse(result.ModelState.IsValid);

    Assert.AreEquals(result.ViewName, viewNameResult);

}

Upvotes: 1

Spock
Spock

Reputation: 6992

What you really want to test is the end result. For example you might want to make sure the correct ViewName returned when the model state is invalid. You would only test one thing.

If you do this in a TDD way ...

Start with a failing test. I only care about the expected outcome if the model state is not valid. In the case I want a specific view to be returned if the model state is not valid.

Unit Test:

   [TestMethod]
    public void CreateFocus_WhenModelStateIsNotValid_ReturnsViewNameCreateFocus()
    {
       // Arrange                    
        var stubFocusService = new Mock<IFocusService>();
        var controller = new FocusController(stubFocusService.Object);
        controller.ModelState.AddModelError("", "fake error message");
        const string expectedViewNameWhenModelError = "CreateFocus";

        // Act
        var result = controller.CreateFocus(It.IsAny<FocusFormModel>()) as ViewResult;

        // Assert 
        Assert.AreEqual(expectedViewNameWhenModelError, result.ViewName);
    }

System Under Test:

    [HttpPost]
    public ActionResult CreateFocus(FocusFormModel focus)
    {
        return new EmptyResult();
    }

Now write just enough production code to make the test pass

    [HttpPost]
    public ActionResult CreateFocus(FocusFormModel focus)
    {
        if (!ModelState.IsValid)
        {
            return View("CreateFocus", focus);
        }

         return new EmptyResult();
    }

The test passes. We are not done yet. We also want to test we get the expected view if the model state is valid. We write a different test for that.

Start with a failing test

   [TestMethod]
    public void CreateFocus_WhenModelStateIsValid_EnsureRouteNameFocus()
    {
        // Arrange                    
        var stubFocusService = new Mock<IFocusService>();
        stubFocusService.Setup(x => x.GetFocus(It.IsAny<string>())).Returns(new Focus());
        var controller = new FocusController(stubFocusService.Object);
        const string expectedRouteNameWhenNoModelError = "Focus";

        // Act
        var result = controller.CreateFocus(new FocusFormModel()) as RedirectToRouteResult;

        // Assert 
        Assert.AreEqual(expectedRouteNameWhenNoModelError, result.RouteValues["action"]);
    }

We refactor the production code to make the test pass. Again what absolutely necessary..

    [HttpPost]
    public ActionResult CreateFocus(FocusFormModel focus)
    {
        if (!ModelState.IsValid)
        {
            return View("CreateFocus", focus);
        }

        var createdfocus = _focusService.GetFocus(focus.FocusName);
        return RedirectToAction("Focus", new { id = createdfocus.FocusId });
    }

Now run all tests and ensure they all pass. Refactor your tests to remove any duplication.

Note that you probably use NUnit or other, but I demonstrated using MSTest. I would have used hand written stubs, but mocking is straightforward here with Moq. I also switched the Model.IsValid to !Model.IsValid (just for my convenience), but you get the idea.

Hope this helps.

Upvotes: 1

Related Questions