Mocking ASP.NET MVC HtmlHelper extension methods using Moq

by Ben Hart 17. October 2008 06:43

I'm in the process of upgrading our ASP.NET MVC Preview 5 app to Beta. Been quite painless so far, but hit a snag with an unmentioned change to the signature of the ViewContext class.

We've followed many by monkeypatching the HtmlHelper class further, extending it to a variety of uses. Obviously we need to test these extensions, so need a reference to an HtmlHelper instance.  We used to have the following helper method to get an HtmlHelper object:

public HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData)
{
    var sw = new StringWriter();
    var rd = new RouteData();
    var tc = new TestController();
    var td = new TempDataDictionary();
    var tv = new TestView();
    var req = new HttpRequest("", "http://localhost/", "");
    var res = new HttpResponse(sw);
    var hc = new HttpContext(req, res);
    var hcw = new HttpContextWrapper(hc);
    var rc = new RequestContext(hcw, rd);
    var cc = new ControllerContext(rc, tc);
    var vc = new ViewContext(cc, "View", viewData, td);
 
    return new HtmlHelper(vc, tv);
}

I'd been aware of this method (had stumbled across it when certain tests seemed to be taking longer than they should), thought it looked pretty dodgy, ignored it, and added it to the growing list of technical debt. "Well it isn't broken..." I've joked with my teammate responsible about adding it the daily wtf, but we've both agreed we've both seen worse.

The beta of ASP.NET MVC has changed the ViewContext constructor to now require an IView and not a view name string, which fortunately broke the above, allowing me to reclaim some debt. We're using Moq, which allowed the following, much simpler, method.

public static HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData)
{
    var mockViewContext = new Mock<ViewContext>(new Mock<HttpContextBase>().Object, 
                                                    new RouteData(), 
                                                    new Mock<ControllerBase>().Object, 
                                                    new Mock<IView>().Object, 
                                                    viewData,
                                                    new TempDataDictionary());
 
    var mockViewDataContainer = new Mock<IViewDataContainer>();
    mockViewDataContainer.Expect(v => v.ViewData).Returns(viewData);
 
    return new HtmlHelper(mockViewContext.Object, mockViewDataContainer.Object);
}

The HtmlHelper requires a ViewContext, and an IViewDataContainer. Mocking the ViewContext is clearly the most work, but not that dificult. In certain circumstances the HtmlHelper needs to get the ViewDataDictionary off the IViewDataContainer, so the Mock of the container above returns the one we fill with test data, which is passed into the method. This might not be necessary, depending on your situation.

If your helper extensions need more from the HttpContext (such as the Request), obviously you'll need to set those expectations too, ours currently don't. Ben Hall has an similar implementation using RhinoMocks (which would need to be changed to cater for the IView requirement), which sets expectations allowing the Resolve method to be used.

Update: I've since realised that one doesn't really 'Mock' an extension method (or any method for that matter), so don't call me out on the title!

Technorati Tags: ,,

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

ASP.NET MVC | TDD

NHibernate objects dirty on load with nothing set in between

by Ben Hart 9. October 2008 14:35

Or "a tale of a day spent debugging".

We've advanced quite far along in quite a big application using NHibernate. I keep an eye on the statements NHibernate generates, making sure that nothing too untoward is happening. I was inspecting one test (not so great to begin with, one that got everything from a database, just to retrieve the first one), and I noticed that every single item being loaded was being updated on flush.

Hmmmm. Doesn't seem right, my honed QA skills told me. Let's take a look. Doesn't look like anything's set. Let's put some breakpoints in the constructors, and take it from there. Hmmm, this is a mighty big object, loads of children, must be one of them setting something on construction. Strange, nothing there. And so my day continued.

To illustrate, let me introduce you to my friend, Enummy. He's quite simple, only really has an id and an emotion.

public class Enummy
{
    public virtual int Id { get; set; }
    public virtual Emotions Emotion { get; set; }
}
 
public enum Emotions
{
    Happy,
    Sad,
    Frustated,
    Annoyed
}

Naturally we should test that he has the range of emotions, so we had whapped out the following tests.

[Test]
public void Enummy_should_be_happy_before_and_after_save()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy {Emotion = Emotions.Happy};
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.That(loaded.Emotion, Is.EqualTo(Emotions.Happy));
}
 
[Test]
public void Enummy_should_sometimes_be_sad()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy {Emotion = Emotions.Sad};
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.That(loaded.Emotion, Is.EqualTo(Emotions.Sad));
}

So far so good. That's the complete round trip, right there, all the way to the database. A proper integration test against SQL Server. We mapped Enummy with the following mapping:

<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.Enummy, Domain" table="Enummy">
    <id name="Id" type="System.Int32" column="Id" unsaved-value="0">
      <generator class="hilo" />
    </id>
    <property name="Emotion" type="System.Int32" />
  </class>
</hibernate-mapping>

Test pass, so all seems good. Except, of course, for the dirty session (one test you're not likely to write).

[Test]
public void Enummy_should_not_dirty_the_session_when_he_loads()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy { Emotion = Emotions.Frustated };
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.IsFalse(session.IsDirty());
}

Enummy, little guy, I realise you're frustrated, but must you dirty yourself and the session so? You even do it when you're happy! Assert.That(debugger.Emotion, Is.EqualTo(Emotions.Annoyed)) :(

The problem is with that mapping. An easy mistake to make, especially if you don't realise that NHibernate has no issues with .NET enums, and presumed that an integer (or even smaller) would be appropriate. Enums should be mapped like so, with the full type of the enum specified.

<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.Enummy, Domain" table="Enummy">
    <id name="Id" type="System.Int32" column="Id" unsaved-value="0">
      <generator class="hilo" />
    </id>
    <property name="Emotion" type="Domain.Emotions, Domain" />
  </class>
</hibernate-mapping>

The cast from Int32 to an enum (albeit implicit at times) is enough to dirty the object. To reiterate (in the interests of good SEO), an NHibernate entity and session will be marked dirty even if nothing is explicitly set if there is a cast from one type to another in the mapping. The values might be equal when one is cast to the type of the other. Until one is cast, though, they are unequal, and as such the object is considered to be dirty. While really hard to debug, I suppose this is the correct behaviour. And once you know about it, debugger.Emotion = Emotions.Happy.

Another item on the code review checklist, watch for enums and other casts in NHibernate mappings, and add an integration test that the object is not dirty straight after a load (for that next newbie to NH who doesn't realise this).

Update: After having struggled through this all on my own, I was relieved to find that I wasn't the only one. I wish I had stumbled across metro-dev extraordinaire Justice Gray's post explaining the issue before. By his reckoning I'm in the 0.0000000000000001%. I like to think I'm even more special.

Technorati Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

C# | NHibernate | TDD

Running unit tests from the keyboard with ReSharper

by Ben Hart 16. September 2008 15:01

I’m a huge ReSharper fan. I consider it an essential tool. Without it you’re simply wasting your own time (or that of your employer, depending on how you see such things). Apart from extending Visual Studio with much better snippets, essential refactoring, static code analysis, intelligent code completion and much, much more, it allows you to spend so much more time without grabbing the mouse. Not just party-trick stuff; functionality that empowers you, tasks that could take hours, done in seconds.

Uhhh, I know all about ReSharper, just not how to run Unit Tests from the keyboard!

Of course never leaving the keyboard means using it for more than typing. This is typically either achieved through context menus (often displayed with the little used (and now I notice absent from my laptop) properties key), or, better, shortcut keys. Visual Studio is jam-packed with shortcuts, and the time spent learning them will pay off ten fold.

What ReSharper really gets, though, is context. Try refactor any piece of code through Ctrl-Shift-R, and the options appropriate to the member are filtered. Suggestions for code completion have insight into surroundings, suggest variable names I hadn't thought of yet, but actually wanted.

Action menuLight-bulbs appear, allowing the convenient Alt+Enter, a shortcut to actions that you might wish to perform.

A further bonus of ReSharper is the Unit Test Runner. Many argue that it’s not the best (it probably isn’t), but it suits my needs, and is included in an already required tool. One feature I really like (that I haven’t noticed elsewhere) is the console output, “remembered” by test. Using NHibernate, for one, I can easily examine the SQL being output, and peruse these statements conveniently long after the suite has run.

Resharper Console Out

Yet despite the R# devs clearly having spent much time in the keyboard ninja dojo, it isn't immediately obvious how to simply run the tests you're working on from the keyboard. I put effort into keeping my tests focused and quick. I don’t want to have to stop coding and grab the mouse to see how my tests are going. ReSharper gets context, it really does. How did they forget this?

It's simpler than I realised. Embarrassingly obvious.

Visual Studio, of course, has the ability to bind shortcuts to actions (Tools - Options - Environment - Keyboard). Filtering these by “Resharper.UnitTest_” shows the applicable commands.

Resharper Keybindings

"ReSharper.UnitTest_ContextDebug", "ReSharper.UnitTest_ContextProfile", "ReSharper.UnitTest_ContextRun" debug, profile or run tests contextually. If the caret is within a test, only that test will be run, within a fixture (but not individual test) all tests in the fixture will be run.

ReSharper allows the selecting of tests to be run in sessions, arbitrary collections of tests you build up. "ReSharper.UnitTest_RunCurrentSession" will run all tests in the session currently selected (topmost in the window), and "ReSharper.UnitTest_RunSolution" will, well, run all the tests in the solution.

The hardest part is finding combinations of keys that aren't already assigned.

Technorati Tags: ,

Thanks to Duncan for the heads up on this one.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

ReSharper | TDD

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About me...

I'm a passionate .NET developer, with C# my language of choice. I've been at it for a number of years now, and enjoy that I'll never shake the feeling I'm just starting out.

I love software, and I love building it even more. I love knowing that my work facilitates others', and that one line of code at a time, we're increasing our capability.

More...



Page List