Three Is It

Because two isn't enough and four is just too many

The legitimate powers of government extend only to such acts as are injurious to others. But it does me no injury for my neighbor to say there are twenty gods, or no God. It neither picks my pocket nor breaks my leg.
Thomas Jefferson
Home Blogs Genealogy Brad's Bookshelf Subscriptions Contact Sign in
 

About the author

Brad Butts is a .NET developer and architect. He is married with children and enjoys reading, working out, and genealogy is his five minutes of spare time.
E-mail me Send mail
National Debt Clock

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Unit Testing in ASP.NET

I’m all for unit testing and even Test Driven Development (although I can do without some of the TDD zealotry out there).  Recently, I began writing some libraries for ASP.NET applications and really struggled with how I was going to write my unit tests to test my work.  Certainly, we have the MVP and MVC patterns and lots of great examples, frameworks, and miscellaneous tools to help you implement these patterns and greatly simplify the unit tests you need to write—but I’m writing an ASP.NET-based library, not an end-to-end application.

Here’s what I’m doing:

I have to write components around a commercial security device that injects certain identity information into the request header of http requests (via an ISAPI filter).  Specifically, I am writing an HttpModule (a few, actually, for different scenarios) to build out a custom Identity object and insert it into the Current.User object.  Additionally, I’m writing a couple of RoleProviders to manage the roles returned by the device.

These components obviously have a tight reliance on HttpContext, so how do you unit test under such a scenario?  Incidentally, these components have to work with a pure ASP.NET 2.0 solution, which means I don’t have access to the new System.Web.Abstractions assembly which is supposed to make testing in this space much easier—I’d still like to see what the Abstractions assembly could do for me, though.

Phil Haack offered up this effort at mocking HttpContext, but I simply couldn’t get that to work for my scenario.  I then looked at TypeMock.  Here’s some code I wrote against TypeMock that still doesn’t work:

[Test]
[VerifyMocks]
public void TestOnAuthenticateRequest2()
{
string fname = "John";
string lname = "Smith";
string fullName = fname + " " + lname;
string roles = "cn=Role1,ou=APPS,o=SomeCompany:cn=Role2,ou=APPS,o=SomeCompany";
string login = "jxsmith";
string commonName = "Smith";
//TestedImplementation is an alias for my custom HttpModule
TestedImplementation target = new TestedImplementation();
MockObject<HttpApplication> applicationMock = MockManager.MockObject<HttpApplication>();
MockedEvent authRequestEvent = applicationMock.ExpectAddEvent("AuthenticateRequest");
target.Init(applicationMock.Object);
MockObject<HttpRequest> httpRequestMock = MockManager.MockObject<HttpRequest>();
applicationMock.ExpectGetAlways("Request", httpRequestMock.Object);
MockObject<HttpResponse> httpResponseMock = MockManager.MockObject<HttpResponse>();
applicationMock.ExpectGetAlways("Response", httpResponseMock.Object);
httpRequestMock.ExpectGetAlways("HttpMethod", "POST");
httpRequestMock.ExpectGetAlways("AppRelativeCurrentExecutionFilePath", "foobar.aspx");
httpRequestMock.Object.Headers.Add("LOGIN", login);
httpRequestMock.Object.Headers.Add("FIRSTNAME", fname);
httpRequestMock.Object.Headers.Add("LASTNAME", lname);
httpRequestMock.Object.Headers.Add("ROLE", roles);
httpRequestMock.Object.Headers.Add("COMMONNAME", commonName);
authRequestEvent.Fire(applicationMock.Object, EventArgs.Empty);
//once I can actually mock the context, I'll write some asserts
}

As I recall, I could never figure out how to stub in data to the request header of the mocked request.  Doing this is pretty important as my components behave differently based on the data present (or not present) in the header.  A sister project to TypeMock called Ivonna (no pun intended) actually might let me do this, though.  Of course, both these products cost money—so, I’d still like to find a free answer to my problem.

Some folks have suggested refactoring my IHttpModule and RoleProvider implementations such that I move as much code as possible out of the direct implementations and into separate classes that can be more easily tested.  I assume they mean separate private classes that aren’t exposed to clients using my components.  To that end, here’s what I tried in my implementation:

public class MyHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
private void OnAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication httpApp = sender as HttpApplication;
MyHttpModuleHelper helper = new MyHttpModuleHelper();
helper.DoStuff(httpApp.Request.Headers);
}
public void Dispose()
{
//dispose stuff here
}
}
//the private class that can be tested
class MyHttpModuleHelper
{
public string[] DoStuff(NameValueCollection headers)
{
//process the headers in some way
return new[]{"hello world"};
}
}

And in my unit test class:

private MethodInfo DoStuffMethod;
private object MyHttpModuleHelper;
[SetUp]
public void Init()
{
Assembly testAssembly = Assembly.GetAssembly(typeof (MyHttpModule));
MyHttpModuleHelper =
testAssembly.CreateInstance("MyNamespace.HttpModules.MyHttpModuleHelper");
Type helperType = MyHttpModuleHelper.GetType();
BindingFlags bf = BindingFlags.Instance | BindingFlags.Public;
DoStuffMethod = helperType.GetMethod("DoStuff", bf);
}
[Test]
public void DoStuffTest()
{
NameValueCollection stubHeaders = new NameValueCollection();
//stubHeaders.Add();  //add some fake data
object returnValue = DoStuffMethod.Invoke(MyHttpModuleHelper, new object[] { stubHeaders });
Assert.IsTrue(returnValue.GetType().IsArray, "Expected the type to be a string array");
string[] roleArray = returnValue as string[];
Assert.IsTrue(roleArray.Length == 1, "Expected the array length to be 1");
StringAssert.IsMatch(roleArray[0], "hello world", "Expected the first value to be hello world");
}

This works, but it’s such a pain.  One frustration I’ve had is that I tend to refactor my helper methods a lot—the parameters I pass, the return values I return, even the names of the methods themselves.  Every time I refactor, I have to go back to my unit tests and refactor them accordingly—even Resharper isn’t smart enough to cascade through such refactoring when I’m using Reflection to invoke those methods.  Do I have any other options?

Virtually at the end of my rope, I even contemplated the mildly insane notion of writing a test ASP.NET app to Response.Write out values from my Identity and Principal objects rendered by my HttpModules and RoleProviders…then use a product like Selenium or WatiN to perform assertions on those values in the pages.

Just when I thought all hope was lost, a friend of mine reminded me of the ASP.NET host adapter.  The ASP.NET host adapter allows you to run a unit test that smells a lot more like an integration test—like integration testing, you have to develop a web app within which your test will run.  The big difference, though, is that you can perform assertions on server-side objects. 

You invoke the ASP.NET host adapter by decorating your MSTTest test method with the attribute, [HostType(“ASP.NET”)].  You also have to include the attribute [UrlToTest(“someUrl”)]—where you specify the ASPX page that establishes the server-side context you’d like to test.  You can add one more attribute, AspNetDevelopmentServerHost, to have you test web app run under the Development Server (a.k.a. Cassini) as opposed to IIS.

It’s lame that I need a web app to run a unit test, but I’m desperate, so I’ll play along.  As I started all this work, one obstacle I kept running into was the UrlToTest attribute: it seemed to only understand the “localhost” domain name.  As I mentioned earlier, I’m writing components that have to play nicely under this third party ISAPI filter.  The ISAPI filter will only be invoked when it detects certain domain names it’s configured to listen for.  Localhost is not one of these—if it were, every time anyone would run an app under localhost, the ISAPI filter would invoke a particular set of policies that may or may not have anything to do with your work—frankly, it would just be bad.

Instead, in Development, until we can get a real IP assigned to our app, we create fictitious domain names by mapping 127.0.0.1 to that fictitious name in our local hosts file.  We configure the ISAPI filter to apply our particular policy when it detects this fictitious host name being requested and all is right in the world.  That is, until you try to invoke the ASP.NET host adapter using your fictitious domain name: it seems that the ASP.NET host adapter could care less about what mappings I have in my hosts file.  I even tried my own IP address and it didn’t like that, either.  Finally, I tried my machine name and, ta-da, the ASP.NET host adapter worked and my test ran.  It just so happens that I can use my machine name as one of these fictitious domain names, but I consider that bad form—it tightly associates my machine to the development environment.  Now, even my tests are tightly associated to my machine.  That really stinks—so much for running my tests on a separate build server…they just won’t work.

Here’s an example of one of my test methods:

[TestMethod]
[HostType("ASP.NET")]
[UrlToTest("http://MyMachineName/SomeVirDir/Default.aspx")]
public void IdentityType()
{
Assert.IsNotNull(HttpContext.Current.User, "Current.User object is null");
Assert.IsNotNull(HttpContext.Current.User.Identity, "Current.User.Identity object is null");
Assert.IsInstanceOfType(HttpContext.Current.User.Identity, typeof(MyCustomIdentity),
"Current identity is not of type MyCustomIdentity");
}

Another thing I noted regarding the UrlToTest attribute is that it only likes my “short” machine name—I had to truncate off my DNS suffix in order to get my test to run properly.

So, this is a terrible way to test—my tests are tightly bound to my machine and require a web app of all things to run a simple unit test—but, at least it works on my machine.  The alternatives would be scraping together a few hundred bucks to buy TypeMock/Ivonna (which would come out of my pocket, not my company’s—see here on why that’s the case) or upgrade to .NET 3.5 SP1 with hopes that the new Abstractions assembly can help me out.

For the moment, I’ll stick with this approach.  My next challenge is to figure out how to dynamically change my web configuration between tests to swap out HttpModules and RoleProviders, as I need to test a few of these.  Any ideas?

Currently rated 2.5 by 4 people

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

Posted by Brad on Saturday, February 21, 2009 7:14 PM
Permalink | Comments (7) | Post RSSRSS comment feed

Thinking about Logging

I think about logging pretty often as I code solutions.  To me, logging is an important part of a quality software product.  In the middle of the night, when your software goes haywire, being able to explore a well-detailed application log file (not just the event logs or IIS logs, for example) can be critical in remediating the problem and reducing the number of lost hours of sleep.

I think a good logging solution should include:

  1. The ability to be turned off or on from outside the compiled solution (eg. from the configuration file).
  2. Persistence ignorance—maybe today I want to log to a flat file, but tomorrow I want to log to a database.  I should be able to easily swap persistence providers without recompilation.  With regard to flat file logging, I’d like the option to be able to specify a relative path or absolute path for the log file (although, for web apps, that could serve as a security vulnerability).  Also, a rolling log file provider is a must.
  3. The ability to crank up or crank down the verbosity of the logging solution. 
  4. Some control over the look of the log file.  Although I might, on occasion, crack open a log file in notepad and manually walk through it, most log files I deal with are quite large—I’d rather use tools like LogParser to produce human-readable reports from the generally-not-human-readable log file.  By having some control over the data that’s added to my log files and how that data is delimited, it can be easier for me to write scripts that render powerful reports (some day, I do hope to put up a post on how I’ve used LogParser to produce meaningful reports based on my application logs).

I could also add to the list that the solution must be hidden behind an interface for easy injection via Dependency Injection.  The easy winner here, in my opinion, is log4net.  If you’re not familiar with log4net, it is a light, open-source API that meets all of these needs and more.  Here are my quick notes on implementing the library:

Step 1: Configure it

…in your application configuration file

<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<!-- A1 is set to be a ConsoleAppender -->
<appender name="A1" type="log4net.Appender.RollingFileAppender">
<file value="logs\log4net.log"/>
<appendToFile value="true"/>
<datePattern value="yyyyMMdd"/>
<rollingStyle value="Date"/>
<!-- A1 uses PatternLayout -->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-4timestamp|%thread|%-5level|%logger|%ndc|%message%newline"/>
</layout>
</appender>
<!-- Set root logger level to DEBUG and its only appender to A1 -->
<root>
<level value="ALL"/>
<appender-ref ref="A1"/>
</root>
</log4net>

…and in your AssemblyInfo.cs file

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

Step 2: Add a reference to log4net.dll

Step 3: Code Away

using System;
using System.Reflection;
using log4net;
namespace WebApplication1
{
public partial class log4net : System.Web.UI.Page
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected void logButton_Click(object sender, EventArgs e)
{
log.Debug("some Debug msg");
log.Error("some Error msg");
log.Fatal("some Fatal msg");
log.Info("some Info msg");
log.Warn("some Warn msg");
}
}
}

 

So, log4net’s great for your everyday application needs, but I have a new requirement: most of what I do anymore is write libraries and frameworks for others to use.  I want to litter my APIs with lots of log messages so that, should a developer want to know what my API is doing, he should just be able to flip a configuration switch and get a log of the activities of my API.  It seems a little presumptuous of me to introduce yet one more dependency on my customer—the log4net dependency—just to be able to use my API.  In this situation, I’d prefer to be able to leverage any kind of logging opportunities in the base class library itself.  The System.Diagnostics namespace gives me some of this opportunity, but none of the native options are as rich or powerful as log4net.

System.Diagnostics.TraceSource

I find the newer System.Diagnostics.TraceSource not even a viable option for me.  TraceSource seems nothing more than the older System.Diagnostics.Trace with less options (eg. no throttling).  So, let’s skip TraceSource and talk about Trace.

 

System.Diagnostics.Trace

It used to be that I would configure a TraceListener like this:

    <system.diagnostics>
<switches>
<add name="MagicTraceSwitch" value="Verbose"/>
</switches>
        <trace autoflush="true" indentsize="4">
<listeners>
<clear/>
<add name="fileListener" 
type="System.Diagnostics.TextWriterTraceListener" 
initializeData="logs\SysDiag.log" 
traceOutputOptions="DateTime"/>
            </listeners>
</trace>
    </system.diagnostics>

Then I’d start coding against the namespace as such:

public partial class _Default : System.Web.UI.Page
{
private TraceSwitch MyTraceSwitch = new TraceSwitch("MagicTraceSwitch", "switch from config file");
protected void Button1_Click(object sender, EventArgs e)
{
Trace.WriteLineIf(MyTraceSwitch.TraceInfo, "log some informational message.");
Trace.WriteLineIf(MyTraceSwitch.TraceVerbose, "log some verbose message.");
}
}

Since System.Diagnostics is part of the BCL, no extra dependencies are required and I can ship just my APIs with no other third party assemblies.  I have at least two problems with this approach, though: 1) the TextWriterTraceListener has no rolling log file capabilities and 2) the way I’m pushing messages out to the listener, via the Trace.WriteLineIf statement, means that only the exact string message in my argument gets inserted into my log—no nicely delimited line with timestamp, thread id, etc.

To address my second problem, I could use a different set of methods in the Trace class:

Trace.TraceError("an error happened.");
Trace.TraceInformation("this is an information trace {0} {1}", "parm1", "parm2");
Trace.TraceWarning("this is a warning trace {0} {1}", "parm1", "parm2");

Interestingly, though, these methods don’t throttle—they pay no attention to my TraceSwitch setting.  All these messages will get logged regardless of the verbosity setting.  The log entries will, however, include the values “Error”, “Warning”, and “Information” if that’s any consolation.  To format the log entry, the listener configuration includes a “Delimiter” attribute and a traceOutputOptions attribute.

To address the first problem, I’ve recently discovered the Microsoft.VisualBasic.Logging.FileLogTraceListener, thanks to this post on StackOverflow.  Here’s a configuration I’ve used for this listener:

    <system.diagnostics>
<switches>
<add name="MagicTraceSwitch" value="Warning"/>
</switches>
        <trace autoflush="true" indentsize="4">
<listeners>
<clear/>
<!--<add name="fileListener" 
type="System.Diagnostics.TextWriterTraceListener" 
initializeData="logs\SysDiag.log" 
traceOutputOptions="DateTime"/>-->
<add 
name="fileListener2" 
type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" 
CustomLocation="C:\data_files\app_development\LoggingTest\WebApplication1\logs"  
Location="Custom" 
BaseFileName="MyApp" 
LogFileCreationSchedule="Daily" 
Delimiter="|" 
traceOutputOptions="ProcessId, DateTime"/>
</listeners>
</trace>
    </system.diagnostics>

One thing to note about this listener is that, unlike the TextWriterTraceListener, the FileLogTraceListener seems to only accept an absolute file path, not a relative one—if you can deal with that, this listener seems to be a viable option—although if I were writing an end-to-end application, not just a framework, I’d definitely go with log4net for my logging solution.

Be the first to rate this post

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

Categories: Technology Blog
Posted by Brad on Saturday, February 21, 2009 3:15 PM
Permalink | Comments (1) | Post RSSRSS comment feed