Fickle Bits

You're doing it wrong.

Testing TempData in ASP.NET MVC

Eventually when testing your controllers you will come across an action that sets TempData.

If you didn’t know, TempData is a session-backed temporary storage dictionary that is available for one single request.  It’s great to pass messages between controllers.

Our Controller classes that we derive from have this as a member property.  However, when testing the controller, this always came up as null.

Reflector is our friend here, so I fired it up and loaded the Controller class.  Then I located the TempData member, and looked at the setter.  It’s marked as internal, so somebody inside this assembly is responsible for creating it.  By using the Reflector Analyze feature, I was able to find exactly where the setter is called.  It’s in the Execute() method.  (this is the general “pipeline” method that all `IController` implementations must override)

Think about that for a second.  At runtime, the TempData dictionary is set because all requests flow through `Execute()`.  But in my test, I am specifically calling my action method.  There is no `Execute()` in my test.

To top it off, I can’t set this guy to a new TempDataDictionary because the setter is marked as internal.

Phil Haack tells me they’re working on this issue so we’ll likely see an improvement soon.  In the meantime you can get around the issue by using reflection to set the value.  But first, can we create one?

TempData is stored in session, so it needs a reference to IHttpContext.  Thankfully, we can mock this, however we have to make sure and mock the right parts.

First, create a mock for IHttpContext, create the return values for mocked IHttpRequest & IHttpResponse.  Finally setup return values for IHttpSession as well.  I’ve done all of this in a helper method of my test (though it could be an extention method on the MockRepository ala Phil, but whatever you like).

1
private IHttpContext GetHttpContext(string requestUrl){  IHttpRequest request = _mocks.DynamicMock<IHttpRequest>();    SetupResult.For(request.Url).Return(new Uri(requestUrl));   IHttpResponse response = _mocks.DynamicMock<IHttpResponse>(); IHttpSessionState session = _mocks.DynamicMock<IHttpSessionState>();IHttpContext httpContext = _mocks.DynamicMock<IHttpContext>();  SetupResult.For(httpContext.Session).Return(session);   SetupResult.For(httpContext.Request).Return(request);   SetupResult.For(httpContext.Response).Return(response);_mocks.Replay(request);_mocks.Replay(response);_mocks.Replay(httpContext);<br><br>_tempData = new TempDataDictionary(httpContext);<br>SetupResult.For(session[null]).IgnoreArguments().Return(_tempData);           <br>_mocks.Replay(session);<br><br>  return httpContext;}

This code is a little verbose, but this is something you write once and use in all of your tests. You should notice that the `_tempData` property is a local field on my test class. I can then take this `_tempData` variable and inject it (via reflection) onto the controller under test. Here’s how:

1
typeof(FooController).GetProperty("TempData").SetValue(controller, _tempData, null);

And now we can easily test that messages are set in TempData before redirecting or rendering a view.

*update*:  Will Shaver notified me of a typo in the code.  It has been fixed.  Thanks Will!*

Comments