# Sunday, 22 May 2011

Last weekend I presented a session at the third annual Chicago Code Camp entitled: Test-driven Development in .NET – Tips, Tools, and Techniques.  The session was standing room only and carried into the commons area afterwards with some excellent questions from attendees.  What is greater than that is that the questions didn’t stop there, they kept coming via email.  One question in particular has stuck with me because it was a hurdle I myself had to overcome early on in my TDD adventures.

The question boiled down to how do you test code the depends on ASP.NET, specifically Web Configuration and Application Settings?  So I thought I would put together a blog post how this can be done, and how it should be done.

To set the stage…I have a simple ASP.NET web application with one page and one class.  The page load method calls the one method in the class that returns an app setting stored in the web config file.

Index.aspx.cs
  1. namespace WebApp
  2. {
  3.     public partial class Index : System.Web.UI.Page
  4.     {
  5.         protected void Page_Load(object sender, EventArgs e) {
  6.             lblValue.Text = SomeClassThatNeedsTesting.GetSomeStringValue();
  7.         }
  8.     }
  9. }

SomeClassThatNeedsTesting.cs
  1. namespace WebApp
  2. {
  3.     public static class SomeClassThatNeedsTesting
  4.     {
  5.         public static string GetSomeStringValue() {
  6.             return System.Web.Configuration.WebConfigurationManager.AppSettings["MyAppSettingValue"];
  7.         }
  8.     }
  9. }

Your first option to getting your tests to run and return the configuration and/or app settings is to tell your tests to run under the ASP.NET host process and not the VSTest host process and also tell what url to run the test under.

SomeClassThatNeedsTesting_Test
  1. [TestMethod]
  2. [HostType("ASP.NET")]
  3. [UrlToTest("http://localhost:2356/Index.aspx")]
  4. [AspNetDevelopmentServerHost("$(SolutionDir)\\WebApp")]
  5. public void GetSomeStringValue_ShouldReturn_NonNull_NonEmpty_String() {
  6.     var val = WebApp.SomeClassThatNeedsTesting.GetSomeStringValue();
  7.     Console.WriteLine(val.ToString());
  8.     Assert.IsFalse(string.IsNullOrEmpty(val));
  9. }

If you run this you will see that the test passes…eventually…after Casini is started and the test runner twiddles its thumbs for a bit.  Speed is my first issue with setting up your test to run in this fashion.  My second issue is that you aren’t testing the ASP.NET configuration framework so why would you need to add all this extra plumbing to make your code take that route?  There is a better option!

Since we really don’t care how or where are value comes from we just need some sort of value returned so we can test our method, we can easily add a thin layer of abstraction and mock the pieces of code that depend on ASP.NET.

Lets start by creating an interface for a simple class that reads application settings.

IAppSettingsReader.cs
  1.  
  2. namespace WebApp {
  3.  
  4.     public interface IAppSettingsReader {
  5.  
  6.         string GetAppSettingValue(string settingKey);
  7.     }
  8. }

Now we can implement the interface and add the actual code to read the app settings from the web config file.

AppSettingsReader
  1. namespace WebApp {
  2.  
  3.     public class AppSettingsReader : IAppSettingsReader {
  4.  
  5.         public string GetAppSettingValue(string settingKey) {
  6.             if (string.IsNullOrEmpty(settingKey)) throw new NullReferenceException("settingKey is required");
  7.             return (WebConfigurationManager.AppSettings[settingKey]);
  8.         }
  9.  
  10.     }
  11. }

Next we will modify our class that needs testing to use the new AppSettingsReader.  Because our goal is to eventually be able to mock the settings reader we need to invert the control of the dependency and provide a mechanism for injecting the dependency.  The change is fairly straightforward.

SomeClassThatNeedsTesting
  1. namespace WebApp {
  2.  
  3.     public class SomeClassThatNeedsTesting {
  4.  
  5.         private readonly IAppSettingsReader _settingsReader;
  6.  
  7.         public SomeClassThatNeedsTesting(IAppSettingsReader settingsReader) {
  8.             _settingsReader = settingsReader;
  9.         }
  10.  
  11.         public string GetSomeStringValue() {
  12.             return _settingsReader.GetAppSettingValue("MyAppSettingValue");
  13.         }
  14.     }
  15. }

Now we have introduced a breaking change and need to modify our page and our test to accommodate the change to SomeClassThatNeedsTesting.

Index.aspx.cs
  1. namespace WebApp {
  2.  
  3.     public partial class Index : Page {
  4.  
  5.         protected void Page_Load(object sender, EventArgs e) {
  6.             var someClass = new SomeClassThatNeedsTesting(new AppSettingsReader());
  7.             lblValue.Text = someClass.GetSomeStringValue();
  8.         }
  9.     }
  10. }

SomeClassThatNeedsTesting_Test
  1. namespace TestProject {
  2.  
  3.     [TestClass]
  4.     public class SomeClassThatNeedsTesting_Tests {
  5.  
  6.         [TestMethod]
  7.         [HostType("ASP.NET")]
  8.         [UrlToTest("http://localhost:2356/Index.aspx")]
  9.         [AspNetDevelopmentServerHost("$(SolutionDir)\\WebApp")]
  10.         public void GetSomeStringValue_ShouldReturn_NonNull_NonEmpty_String_ASP() {
  11.             SomeClassThatNeedsTesting someClass = new SomeClassThatNeedsTesting(new AppSettingsReader());
  12.             string val = someClass.GetSomeStringValue();
  13.             Console.WriteLine(val);
  14.             Assert.IsFalse(string.IsNullOrEmpty(val));
  15.         }
  16.     }
  17. }

At this point we can run our test from above and, after we have taken a bathroom break, checked Twitter, and made a sandwich, we will notice that the test still passes.  This is true because all we have done is add a layer of abstraction.  We have not changed the core functionality of the process.  Now that the layer of abstraction is in place we can write another test and this time instead of writing more code to please ASP.NET lets drop that dependency completely by mocking it.

To implement our mock I am going to use my mocking tool of choice, RhinoMocks.  (I am purposefully not supplying a URL here because you should be using NuGet to get your packages!)

Here is our new and improved test.

New And Improved Test
  1. [TestMethod]
  2. public void GetSomeSringValue_ShouldReturn_NonNull_NonEmpty_String_Mocked() {
  3.     //Arrange
  4.     string myAppSettingValue = "This is my expected mocked setting value";
  5.     MockRepository _repository = new MockRepository();
  6.     IAppSettingsReader mockSettingsReader = MockRepository.GenerateStrictMock<IAppSettingsReader>();
  7.     mockSettingsReader.Expect(msr => msr.GetAppSettingValue("MyAppSettingValue")).Return(myAppSettingValue).Repeat.Once();
  8.     _repository.ReplayAll();
  9.     //Act
  10.     SomeClassThatNeedsTesting someClass = new SomeClassThatNeedsTesting(mockSettingsReader);
  11.     string val = someClass.GetSomeStringValue();
  12.     //Assert
  13.     Assert.AreEqual(myAppSettingValue, val, "Wrong value returned from GetSomeSringValue");
  14. }

If you take a look at the test you can see that the first thing that I did was create my mock AppSettingReader and set my expectations.  After that I instantiated my class under test and passed in the settings reader mock.

Hope you found this useful!  Complete code is available here!

Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview