• Post Calendar

    May 2010
    S M T W T F S
    « Apr   Sep »
     1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031  

Unit Testing CodeSmith Insight

As anyone that knows me knows, I use test driven development. So when I put together a Feedback page in my MVC (Model-View-Controller) site I started with my test. What should the test be?

Let’s start with the User Story : As a site user (anonymous or logged in) I want to submit feedback to the webmaster so that I can get bugs fixed or get features I want.

So the HelpController has a view called Feedback that lets a user input some feedback and submit it using the CodeSmith Insight product.

I finally settled on a test like this…

The Test
  1. [TestCategory("Build"), TestCategory("Unit"), TestMethod]
  2. public void FeedbackReceived()
  3. {
  4.     // Arrange
  5.     string expected = "FeedbackReceived";
  6.     string actual;
  7.     var detail = new CaseReportDetail() { Description = "Test", Title = "Test" };
  8.     var controller = CreateControllerAsNonAdmin(false);
  9.  
  10.     // Act
  11.     var result = controller.Feedback(detail) as ViewResult;
  12.  
  13.     // Assert
  14.     Assert.IsTrue(controller.IsCaseSubmited); // Was the case submitted?
  15.     actual = result.ViewName;
  16.     Assert.AreEqual(expected, actual); // Did it forward to the right view?
  17. }

That seems simple enough, but there’s obviously some hidden details here… What’s a CaseReportDetail? What’s the controller? And what’s the property IsCaseSubmitted?

CaseReportDetail

The CaseReportDetail is merely a model that I leveraged from the samples that came with the CodeSmith Insight. Here is my class:

CaseReportDetail
  1. public class CaseReportDetail
  2. {
  3.     public string Email { get; set; }
  4.     public string Title { get; set; }
  5.     public string Description { get; set; }
  6. }

Ok… that’s pretty simple, now what about the controller?

The Controller

The controller is a bit trickier. That code leverages the Moq (pronounced Mock You) framework. Here is what that code looks like:

Getting HelpController via Moq
  1. private HelpController CreateControllerAsNonAdmin(bool submitCaseFails)
  2. {
  3.     var mock = new Mock<ControllerContext>();
  4.     mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns("test@nowhere.com");
  5.     mock.Setup(p => p.HttpContext.User.IsInRole("Admin")).Returns(false);
  6.     mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
  7.  
  8.     var controller = new HelpController(new MockInsightManager(submitCaseFails));
  9.     controller.ControllerContext = mock.Object;
  10.  
  11.     return controller;
  12. }

But as we drill into that it begs a question, “What is the MockInsightManager?” As with many classes, the InsightManager does not have an interface and has some “non-overrideable” members that make Mocking hard. One trick I use when I encounter that is a wrapper with an interface. So let me show all these items, the IInsightManagerBase, InsightManagerWrapper, and MockInsightManager so you can get a sense for the overall layout:

IInsightManagerBase
  1. public interface IInsightManagerBase
  2. {
  3.     bool IsCaseSubmitted { get; }
  4.     CaseReport CreateCase();
  5.     void SubmitCase(CaseReport caseReport);
  6. }

InsightManagerWrapper
  1. public class InsightManagerWrapper : IInsightManagerBase
  2. {
  3.     public InsightManagerWrapper()
  4.     {
  5.         Manager = InsightManager.Current;
  6.     }
  7.     
  8.     private InsightManager Manager { get; set; }
  9.  
  10.     public bool IsCaseSubmitted
  11.     {
  12.         get;
  13.         private set;
  14.     }
  15.  
  16.     public Exception FailureException { get; set; }
  17.  
  18.     public CaseReport CreateCase()
  19.     {
  20.         return Manager.CreateCase();
  21.     }
  22.  
  23.     public void SubmitCase(CaseReport caseReport)
  24.     {
  25.         try
  26.         {
  27.             Manager.SubmitCase(caseReport);
  28.             IsCaseSubmitted = true;
  29.         }
  30.         catch (Exception ex)
  31.         {
  32.             FailureException = ex;
  33.             IsCaseSubmitted = false;
  34.         }
  35.     }

MockInsightManager
  1. public class MockInsightManager : IInsightManagerBase
  2. {
  3.     public MockInsightManager(bool submitCaseFails)
  4.     {
  5.         SubmitCaseFails = submitCaseFails;
  6.     }
  7.     public bool SubmitCaseFails { get; set; }
  8.     public bool IsCaseSubmitted { get; set; }
  9.  
  10.     public CaseReport CreateCase()
  11.     {
  12.         return InsightManager.Current.CreateCase();
  13.     }
  14.  
  15.     public void SubmitCase(CaseReport caseReport)
  16.     {
  17.         if (!SubmitCaseFails)
  18.         {
  19.             IsCaseSubmitted = true;
  20.         }
  21.     }
  22. }

So now we have a lot of the backup code, what does the actual view code in the controller look like? I’ll first show what it would look like if you just used the insight manager for those that want to see what it would look like when using Insight out of the box:

Feedback method out-of-box
  1. [AcceptVerbs(HttpVerbs.Post)]
  2. public ActionResult Feedback(CaseReportDetail detail)
  3. {
  4.     try
  5.     {
  6.         var report = InsightManager.Current.CreateCase();
  7.         report.EmailAddress = detail.Email;
  8.         report.Title = detail.Title;
  9.         report.Description = detail.Description;
  10.         report.CaseType = CaseType.Inquiry;
  11.         InsightManager.Current.SubmitCase(report);
  12.         return View("FeedbackReceived", detail);
  13.     }
  14.     catch
  15.     {
  16.         ModelState.AddModelError("Feedback", "There was a problem submitting the feedback. You may try to press submit again to try again. We apologize for the inconvenience.");
  17.         return View();
  18.     }
  19. }

With no real indirection above, now let’s look at the controller after I modified it for easier unit testing.

Here’s the top of the controller class where I have a constructor:

HelpController constructor
  1. public class HelpController : Controller
  2. {
  3.     private IInsightManagerBase _insightManager { get; set; }
  4.     public HelpController()
  5.         : this(new InsightManagerWrapper())
  6.     {
  7.     }
  8.     public HelpController(IInsightManagerBase insightManager)
  9.     {
  10.         _insightManager = insightManager;
  11.     }

And here is the modified View method based on this new constructor:

Feedback
  1. [AcceptVerbs(HttpVerbs.Post)]
  2. public ActionResult Feedback(CaseReportDetail detail)
  3. {
  4.     var report = _insightManager.CreateCase();
  5.     report.EmailAddress = detail.Email;
  6.     report.Title = detail.Title;
  7.     report.Description = detail.Description;
  8.     report.CaseType = CaseType.Inquiry;
  9.     _insightManager.SubmitCase(report);
  10.     if (_insightManager.IsCaseSubmitted)
  11.     {
  12.         return View("FeedbackReceived", detail);
  13.     }
  14.     else
  15.     {
  16.         ModelState.AddModelError("Feedback", "There was a problem submitting the feedback. You may try to press submit again to try again. We apologize for the inconvenience.");
  17.         return View();
  18.     }
  19. }

The Negative Case

So now the original test works… but what happens if something goes wrong when submitting the case?

I added a new test that looks like this:

Feedback_Not_Received
  1. [TestCategory("Build"), TestCategory("Unit"), TestMethod]
  2. public void Feedback_Not_Received()
  3. {
  4.     // Arrange
  5.     string expected = "Feedback"; // Did not work so we stayed on the Feedback view showing the error.
  6.     string actual;
  7.     var detail = new CaseReportDetail() { Description = "Test", Title = "Test" };
  8.     var controller = CreateControllerAsNonAdmin(true);
  9.  
  10.     // Act
  11.     var result = controller.Feedback(detail) as ViewResult;
  12.  
  13.     // Assert
  14.     Assert.IsFalse(result.ViewData.ModelState.IsValid); // Was the model invalid?
  15.     actual = result.ViewName;
  16.     Assert.AreEqual(expected, actual); // Did it forward to the right view?
  17. }

Then when I was done, I had 100% code coverage for the Feedback method!

I hope this helps others unit test the CodeSmith Insight…

1 Comment  »

  1. tdupont says:

    Hello,

    Nice post, thanks for sharing your Insight insights with the community. 😉
    How is Insight working out for you?

    Tom DuPont
    Vice President
    CodeSmith Tools, LLC
    http://www.codesmithtools.com

RSS feed for comments on this post, TrackBack URI

Leave a Comment