0

It's been a while since I did any MVC work, so I'm hopefully missing something. I'm trying to write a test and controller action to simply Edit a DTO named "Business".

Controller Action:

[HttpPost]
public ActionResult Edit(string id, Business business)
{
    try
    {
        var model = _businessRepository.Get(id);

        if (model != null)
        {
            UpdateModel(model);

            if (ModelState.IsValid)
            {
                _businessRepository.Save(model);
            }
            else
            {
                return View(business);
            }
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

Test:

[TestMethod]
public void Edit_Post_Action_Updates_Model_And_Redirects()
{
    // Arrange
    var mockBusinessRepository = new Mock<IBusinessRepository>();
    var model = new Business { Id = "1", Name = "Test" };
    var expected = new Business { Id = "1", Name = "Not Test" };

    // Set up result for business repository
    mockBusinessRepository.Setup(m => m.Get(model.Id)).Returns(model);
    mockBusinessRepository.Setup(m => m.Save(expected)).Returns(expected);
    var businessController = new BusinessController(mockBusinessRepository.Object);

    // Act
    var result = businessController.Edit(model.Id, expected) as RedirectToRouteResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(result.RouteValues["action"], "Index");
    mockBusinessRepository.VerifyAll();
}

The line that it is giving an exception on, is the UpdateModel() in the controller. The exception details are:

"Value cannot be null. Parameter name: controllerContext"

mandreko
  • 1,766
  • 2
  • 12
  • 24
  • 1
    is there anything in the model when you call the save method? what is the code for the model? – Brian Apr 11 '12 at 12:59
  • Without the code for `UpdateModel()` it's hard to say, but my *guess* is that it's relying on a db context that's not being constructed by your unit test. – GalacticCowboy Apr 11 '12 at 13:27
  • @Brian I never get to the Save method, because it dies on UpdateModel. But the model is simply 2 strings: Id, and Name. – mandreko Apr 11 '12 at 14:31
  • @GalacticCowboy the UpdateModel() function is built into MVC. I did not write it. There is no db context to construct since I'm not using SQL. I am mocking the repository so that I don't have to even rely on the repositories. Those are tested elsewhere. – mandreko Apr 11 '12 at 14:32

5 Answers5

1

I have some code on Gist that I typically use to set up that ControllerContext. The code is a modified version that was originally taken from Hanselman's blog.

https://gist.github.com/1578697 (MvcMockHelpers.cs)

CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138
  • This too causes a "Object reference not set to an instance of an object." error on UpdateModel. It still doesn't seem to like it. – mandreko Apr 11 '12 at 14:51
1

Setup the Controller context

The following is code snippet from a project which I work on, so maybe it's to much for you

public class TestBase
    {
        internal Mock<HttpContextBase> Context;
        internal Mock<HttpRequestBase> Request;
        internal Mock<HttpResponseBase> Response;
        internal Mock<HttpSessionStateBase> Session;
        internal Mock<HttpServerUtilityBase> Server;
        internal GenericPrincipal User;

            public void SetContext(Controller controller)
            {
              Context = new Mock<HttpContextBase>();
              Request = new Mock<HttpRequestBase>();
              Response = new Mock<HttpResponseBase>();
              Session = new Mock<HttpSessionStateBase>();
              Server = new Mock<HttpServerUtilityBase>();
       User = new GenericPrincipal(new GenericIdentity("test"), new string[0]);

              Context.Setup(ctx => ctx.Request).Returns(Request.Object);
              Context.Setup(ctx => ctx.Response).Returns(Response.Object);
              Context.Setup(ctx => ctx.Session).Returns(Session.Object);
              Context.Setup(ctx => ctx.Server).Returns(Server.Object);
              Context.Setup(ctx => ctx.User).Returns(User);

              Request.Setup(r => r.Cookies).Returns(new HttpCookieCollection());
              Request.Setup(r => r.Form).Returns(new NameValueCollection());
      Request.Setup(q => q.QueryString).Returns(new NameValueCollection());
              Response.Setup(r => r.Cookies).Returns(new HttpCookieCollection());

              var rctx = new RequestContext(Context.Object, new RouteData());
controller.ControllerContext = new ControllerContext(rctx, controller);
            }
}

Then in your tests you can arrange:

//Arrange
SetContext(_controller);
Context.Setup(ctx => ctx.Request).Returns(Request.Object);

If you want to test your method with ModelState errors, add:

_controller.ModelState.AddModelError("Name", "ErrorMessage");
Andrew
  • 5,395
  • 1
  • 27
  • 47
  • While this does look like what I've been reading about, it still causes the code to die on the UpdateModel for some reason. "Object reference not set to an instance of an object." – mandreko Apr 11 '12 at 14:41
  • I see that I've setup the Response, which must be the Request in your case. I think that is also your Null reference exception – Andrew Apr 11 '12 at 18:10
0

You need to mock the ControllerContext for your BusinessController.

See this question or this one.

Community
  • 1
  • 1
Martin
  • 11,031
  • 8
  • 50
  • 77
0

I've managed to get what I wanted working by using Automapper instead of the UpdateModel.

I added in my automapper initialization (IPersistable is an interface for all my DTOs):

Mapper.CreateMap<IPersistable, IPersistable>().ForMember(dto => dto.Id, opt => opt.Ignore());

I then changed my controller action to:

[HttpPost]
public ActionResult Edit(string id, Business business)
{
    try
    {
        var model = _businessRepository.Get(id);

        if (model != null)
        {
            Mapper.Map(business, model);

            if (ModelState.IsValid)
            {
                _businessRepository.Save(model);
            }
            else
            {
                return View(business);
            }
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

And changed my test to:

[TestMethod]
public void Edit_Post_Action_Updates_Model_And_Redirects()
{
    // Arrange
    var mockBusinessRepository = new Mock<IBusinessRepository>();
    var fromDB = new Business { Id = "1", Name = "Test" };
    var expected = new Business { Id = "1", Name = "Not Test" };

    // Set up result for business repository
    mockBusinessRepository.Setup(m => m.Get(fromDB.Id)).Returns(fromDB);
    mockBusinessRepository.Setup(m => m.Save(It.IsAny<Business>())).Returns(expected);
    var businessController = new BusinessController(mockBusinessRepository.Object) {ControllerContext = new ControllerContext()};

    //Act
    var result = businessController.Edit(fromDB.Id, expected) as RedirectToRouteResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(result.RouteValues["action"], "Index");
    mockBusinessRepository.VerifyAll();
}
mandreko
  • 1,766
  • 2
  • 12
  • 24
0

I had the same problem and used the stack trace to pin this down to the ValueProvider. Building on Andrew's answer above for mocking some of the underlying objects used by a controller, I managed to solve the null value exception by also mocking the ValueProvider like this:

var controller = new MyController();

// ... Other code to mock objects used by controller ...

var mockValueProvider = new Mock<IValueProvider>();
controller.ValueProvider = mockValueProvider.Object;

// ... rest of unit test code which relies on UpdateModel(...)
Will Appleby
  • 476
  • 4
  • 17