Saturday, November 21, 2015

How to easily test MVC Controller Actions that use HttpContext

If you're like me, you've heard how awesome MVC is at being testable, but then you see everyone going off and using HttpContext.Request and other methods that make unit testing seemingly impossible or very difficult.  I mean, what's the point of crowing about MVC being testable if it's not very easy to test your code that uses the basic functionality of the framework?  I work on several very large MVC applications and everyone uses HttpContext all over the place, and rightly so.  I was trying to write all kinds of crazy code to try to abstract, inject, etc.  Something just felt wrong.

Well, after much banging my head against the wall, I've found a way to test actions that doesn't require you to stop using HttpContext.  Do realize that this will not fix your code using the static property System.Web.HttpContext.Current.

Let's see some code

Let's go ahead and create a fresh MVC project in Visual Studio 2015.  Make sure to have it create a test project for you, too.
Navigate to the HomeController and make the following changes:

public ActionResult About() {
ViewBag.Message = "Your application description page.";
       ViewBag.Url = HttpContext.Request.RawUrl;
return View();
}

Add the following line in the About view:
@{
    ViewBag.Title = "About";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
<p>You navigated to this url: @ViewBag.Url</p>

<p>Use this area to provide additional information.</p>

Run the project and notice that it displays:




Good, now let's modify the test to make sure that the URL is what we think it should be.
Navigate to the HomeControllerTest file and add the following:

[TestMethod]
public void About() {
// Arrange
HomeController controller = new HomeController();
// Act
ViewResult result = controller.About() as ViewResult;
// Assert
Assert.AreEqual("Your application description page.", result.ViewBag.Message);
Assert.AreEqual("http://localhost:59191/Home/About", result.ViewBag.RawUrl);
}

Now run the test and notice: BANG!
NullReferenceException: Object reference not set to an instance of an object.



It makes sense--you're not in Kansas anymore.  And by Kansas, of course, I mean IIS...or whatever baked my fresh HttpContext before.  So, HttpContext is null. :'(

MvcContrib Test Helper

Install the following package using the NuGet Package Manager:

  1. Right-click the References special folder for the test project and click Manage NuGet Packages...
  2. Search for MvcContrib.TestHelper
  3. Install

Update the test method and run the About test again


using MvcContrib.TestHelper;
...
[TestMethod]
public void About() {
// Arrange
var controller = new HomeController();
var builder = new TestControllerBuilder();
builder.RawUrl = "http://localhost:59191/Home/About";
builder.InitializeController(controller);
// Act
var result = controller.About() as ViewResult;
// Assert
Assert.AreEqual("Your application description page.", result.ViewBag.Message);
Assert.AreEqual("http://localhost:59191/Home/About", result.ViewBag.Url);
}

Success!!!


That's great, but what if I wanted to use Url instead of RawUrl?

I have to admit, I originally wrote this post with Request.Url in mind.  It didn't work--there was no builder.Url hanging around--just RawUrl.  But, I did find a way of making that work, too!

Here's the controller code:
public ActionResult About() {
ViewBag.Message = "Your application description page.";
ViewBag.RawUrl = HttpContext.Request.RawUrl;
ViewBag.Url = HttpContext.Request.Url;
return View();
}

Here's the Test code:
using Rhino.Mocks; //This gets installed with MvcContrib Test Helper
...
[TestMethod]
public void About() {
// Arrange
var controller = new HomeController();
var builder = new TestControllerBuilder();
builder.RawUrl = "http://localhost:59191/Home/About";
builder.InitializeController(controller);
builder.HttpContext.Request.Stub(x => x.Url).Return(new Uri("http://localhost:59191/Home/About"));
// Act
var result = controller.About() as ViewResult;
// Assert
Assert.AreEqual("Your application description page.", result.ViewBag.Message);
Assert.AreEqual("http://localhost:59191/Home/About", result.ViewBag.RawUrl);
Assert.AreEqual("http://localhost:59191/Home/About", result.ViewBag.Url.ToString());
}

Run the test:
BAM! SCORE!

It gets better!

Here's a list of properties hanging off of TestControllerBuilder just begging to be used:
  • Files
  • Form
  • HttpContext
  • PathInfo
  • QueryString
  • RawUrl
  • RouteData
  • Session
  • TempDataDictionary
Here are a few good references on how to use the other features of MvcContrib TestHelper:

Summary

  • MVC is testable
  • We all like to use HttpContext
  • HttpContext is a pain to set up and inject for testing controller actions
  • If you do nothing, HttpContext, Request, Response, etc. will all be null
  • Idea: Use MvcContrib Test Helper
  • Use its easy-access properties where you can
  • Use RhinoMocks to fill in the holes
Do you have something awesome to add to this post?  Great!  Leave a comment.
Do you know of a better way?  Great! Leave a comment.
Did I do something that you hate?  Great!  Leave a comment.
The more we engage, the better we'll all be at our jobs.

Thank you for reading, sharing, and engaging--and God bless!
Brandon

Monday, October 26, 2015

The key element to writing up bugs - Steps to Reproduce

I recently became painfully aware of a valuable lesson and wanted to share it with the rest of the community.
We had a particularly large deploy this time and we ended up having around 260 bugs + features, combined.  I'm the QA guy on the team, so once we deployed to the staging server, it was my job to go through each of those 260 work items and ensure that they were working as intended.  The level of information for each ranged somewhere between a few words and a nice, ordered bulleted list of exactly what to do.

Here are two ways of writing up Steps to Reproduce for a bug.

Here's how not to fill out the Steps to Reproduce section

Steps to Reproduce

The chart links are going to old urls
Here's what I'm thinking every time I open it:
  • What chart links? We have charts all over the product.
  • What were the old URLs?
  • How do I know if we're going to the new URLs?
  • "Hey, you guys!  Do any of you know what's going on here?  Oops, it's lunch time and nobody's here." 

As you can see, that has a very high cognitive load, is error-prone, and is a downright annoying way of QAing a product release.  I mean, you can't just go ahead and assume that it works.  Otherwise, they wouldn't need you to test it.  Now imagine going through this for each bug/product backlog item.  "Woo hoo!! One down, 259 more to go!  At this rate I'll be done in...6 weeks?!?!"


Here's a much better example

Steps to Reproduce

  1. Open a report
  2. Edit the content settings of a search
  3. Click the "enable turbo speed" check box
  4. Notice the dancing squirrel obscuring the save button
  5. We don't like dancing squirrels in our product
Plus, most of mine have a lot of screenshots so that you could see exactly what it looked like for me.  Bug reports like that were nice.  I could breeze through those one after another.  Why?  The biggest reason is because of the low cognitive load.  I didn't have to think!  No, it's not a good idea to force someone to needlessly think.  My job is to make sure that the product doesn't break that way anymore--not figure out how you made it break and then make sure it doesn't break that way anymore.

Here's the Takeaway

The person who writes up the bugs or product backlog items is in a position to really show love or hate to their quality assurance personnel--as well as to the other developers who have to fix it.  Honestly, how much longer does it take to write a bulleted list of instructions on how to reproduce the issue?  I'd say that most would take less than two minutes.  I go crazy with mine and add screenshots even when I probably don't have to--and edit them in Microsoft Paint to highlight exactly what I'm referring to in the screenshot and it still takes me less than two minutes.
Let's compare two minutes up front to about 30 minutes or so each time I want to reproduce this thing.  

Rules of Thumb for filling out Steps to Reproduce
  • Write it out in an ordered list of simple instructions
  • Write it in easy-to-understand language.  The more explicit it is, the easier it is to follow the exact steps to reproduce the issue.
  • If you come across a poorly-written or non-existent Steps to Reproduce, please do everyone a favor and fill it out

Oh, and be nice.  I didn't even think about bug report quality very much until I was in charge of testing.  I was the one who had to experience the pain.  I seriously doubt your teammates want to cause you pain.  It happens.  When they do, just send them a link to this article.

Keep on making this world a better place and may God bless you with grace and peace.

Hey, how about you take a few minutes and introduce yourself in the comments and tell us some of the things you've learned?  Come on--you know you want to!  We'd love to learn from you, too!

Brandon

Friday, October 16, 2015

Welcome!

Welcome to Easy Software Testing!
Test just enough to thrive.

The goal of this blog is to empower you to implement simple, yet powerful, software testing practices!

I prefer the straight-forward, KISS (Keep It Simple, Stupid/Silly) principal.

This site's for you if you write software, but can't afford a dedicated Quality Assurance (QA) person.

Software testing is a must--but easy to go off the rails if you're not careful.
It's easy to do no testing and it's easy to get the testing bug of testing anything that moves!
If you test too much, your software changes a little bit and breaks half of your tests--and then you stop testing altogether.
If you don't test at all, then your deploys are likely fraught with stress and bugs.

My goal is to help you find the "just right" spot of testing.

Thank you for staying with me as we learn this together!

I'm interested in the problems you face, so leave a comment below.