The Artima Developer Community
Sponsored Link

.NET Buzz Forum
NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini...

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Scott Hanselman

Posts: 1031
Nickname: glucopilot
Registered: Aug, 2003

Scott Hanselman is the Chief Architect at Corillian Corporation and the Microsoft RD for Oregon.
NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini... Posted: Dec 2, 2004 4:08 PM
Reply to this message Reply

This post originated from an RSS feed registered with .NET Buzz by Scott Hanselman.
Original Post: NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini...
Feed Title: Scott Hanselman's ComputerZen.com
Feed URL: http://radio-weblogs.com/0106747/rss.xml
Feed Description: Scott Hanselman's ComputerZen.com is a .NET/WebServices/XML Weblog. I offer details of obscurities (internals of ASP.NET, WebServices, XML, etc) and best practices from real world scenarios.
Latest .NET Buzz Posts
Latest .NET Buzz Posts by Scott Hanselman
Latest Posts From Scott Hanselman's ComputerZen.com

Advertisement

There's a lot of info out there on how to cobble together NUnit Unit Testing of ASP.NET Pages and assorted goo. NUnitASP is a nice class library to facilitate this kind of testing, but it doesn't solve a few problems:

  • Do you have/want a Web Server on your Test/Build machine?
  • How do you get your Test Pages and such over to the Web Server? Just automatically copy them?
  • Are your Test cases self-contained? That is, do they require external files and other stuff to be carried along with them?

I have a need to test a number of utility classes, base classes for System.Web.UI.Page and other miscellany and I'd like the tests to be entirely self contained and runnable only with NUnit as a requirement.

So, here's a small solution we use at Corillian. I use Cassini, the tiny ASP.NET Web Server that brokers HTTP Requests to System.Web.Hosting and the ASP.NET Page Renderer. You may know Cassini as the precursor to the Visual Developer Web Server from Visual Studio "Whidbey" 2005. Cassini usually comes with two parts, CassiniWebServer.exe and Cassini.dll.  However, I don't want to launch a executables, so I'll just refer to Cassini.dll as that is the main engine.

using Cassini;

 

    [TestFixture]

    public class WebTests

    {

        private Server webServer;

        private readonly int webServerPort = 8085;

        private readonly string webServerVDir = "/";

        private string tempPath = AppDomain.CurrentDomain.BaseDirectory;

        private string tempBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");

        private string webServerUrl; //built in Setup

Cassini is the 'private Server webServer' in the code above. I'm using a fairly random port, but you could certainly scan for an open port if you like. Note that I'm building a /bin folder, as Cassini's own ASP.NET Hostingn AppDomain will look for DLLs to load in /bin.

Cassini starts up another AppDomain, and that AppDomain then loads Cassini.dll AGAIN, except the new AppDomain has a different search path that includes /bin, so it won't find Cassini.dll in the current directory. Usually this problem is solved by putting Cassini.dll in the GAC, but I want this test to be self-contained, and since I'll need my other DLLs in /bin anyway...

[TestFixtureSetUp]

public void Setup()

{

    //Extract the web.config and test cases (case sensitive!)

    ExtractResource("web.config",tempPath);

    ExtractResource("test1.aspx",tempPath);

    ExtractResource("test2.aspx",tempPath);

 

    //NOTE: Cassini is going to load itself AGAIN into another AppDomain,

    // and will be getting it's Assembliesfrom the BIN, including another copy of itself!

    // Therefore we need to do this step FIRST because I've removed Cassini from the GAC

 

    //Copy our assemblies down into the web server's BIN folder

    Directory.CreateDirectory(tempBinPath);

    foreach(string file in Directory.GetFiles(tempPath,"*.dll"))

    {

        string newFile = Path.Combine(tempBinPath,Path.GetFileName(file));

        if (File.Exists(newFile)){File.Delete(newFile);}

        File.Copy(file,newFile);

    }

 

    //Start the internal Web Server

    webServer = new Server(webServerPort,webServerVDir,tempPath);

    webServerUrl = String.Format("http://localhost:{0}{1}",webServerPort,webServerVDir);

    webServer.Start();

 

    //Let everyone know

    Debug.WriteLine(String.Format("Web Server started on port {0} with VDir {1} in physical directory {2}",webServerPort,webServerVDir,tempPath));

}

A couple of important things to note here. 

  • This method is marked [TestFixtureSetup] rather than [Setup] as we want it to run only once for this whole Assembly, not once per test.
  • We're copying all the DLLs in the current directory down to /bin for Cassini's use during the test, but we'll delete them in [TestFixtureTearDown] after Cassini's AppDomain.Unload.
  • We build webServerUrl and start Cassini (remember, it's the "webServer" variable).
  • At this point we are listing on http://localhost:8085/

Additionally, I've snuck a new method in, ExtractResource(). This takes the name of an Embedded Resource (one that is INSIDE our test assembly) and puts it into a directory. In this case, I'm using the current AppDomain's directory, but you could certainly use Path.GetTempPath() if you like.

private StringCollection extractedFilesToCleanup = new StringCollection();

private string ExtractResource(string filename, string directory)

{

    Assembly a = Assembly.GetExecutingAssembly();

    string filePath = null;

    string path = null;

    using(Stream stream = a.GetManifestResourceStream("Corillian.Voyager.Web.Test." + filename))

    {

        filePath = Path.Combine(directory,filename);

        path = filePath;

        using(StreamWriter outfile = File.CreateText(filePath))

        {

            using (StreamReader infile = new StreamReader(stream))

            {

                outfile.Write(infile.ReadToEnd());   

            }

        }

    }

    extractedFilesToCleanup.Add(filePath);

    return filePath;

}

The ExtractResource method takes a filename and directory (and could, if you like, take a namespace, although I've hardcoded mine) and pulls a file as a Stream of bytes that was embedded as a resource in our Assembly and puts it into a directory. Hence the name, ExtractResource(). There is a StringCollection called extractedFilesToCleanup that keeps track of all the files we'll want to delete at the end of these tests, in [TestFixtureTearDown].

At this point, I've got a web.config (which was important to my tests, and will be looked at by Cassini/System.Web.Hosting) and a test1.aspx and test2.aspx in my current directory. The Cassini Web Server is started up and listing on port 8085 for HttpRequests. I also turned Page Tracing on in the web.config which will allow me to make certain Assertions about what kinds of code were called by the Page and helper classes within the Cassini ASP.NET context.

I'll add a helper method to create HttpRequests and return the response as a string:

private string GetPage(string page)

{

    WebClient client = new WebClient();

    string url = new Uri(new Uri(webServerUrl),page).ToString();

    using (StreamReader reader = new StreamReader(client.OpenRead(url)))

    {

        string result = reader.ReadToEnd();

        return result;

    }

}

Now I can write some tests! My tests need to test things like the overridden behavior of custom BasePages (derived from System.Web.UI.Page) as well as some helper functions that require (to be TRULY tested) an HttpContext. However, I don't want the hassle of a CodeBehind or a lot of other files, and certainly not the effort of a whole separate test project, so I'll make my ASPX pages self contained using <% @Assembly %> directives. So, for example: 

<%@ Assembly Name="Corillian.Voyager.Web.Common" %>
<%@ Page Language="C#" Inherits="Corillian.Voyager.Web.Common.SharedBasePage" %>
<script runat="server">
    public void Page_Load(object sender, EventArgs e )
    {
        Label1.Text = "Hello";
    }
</script>
<html>
  <body>
    <form runat="server">
        <p>
            <asp:Label id="Label1" runat="server">Label
            </asp:Label>
        </p>
    </form>
  </body>
</html>

A test to determine if this page executed successfully might look like:

[Test]

public void BasicSmokeTestOfWebServer()

{

    string result = GetPage("test1.aspx");

    Assert.IsTrue(result.IndexOf("Hello") != -1,"Basic smoke test of test1.aspx didn't find 'Hello' in response!");

}

You could easily expand your tests to include NUnitASP, or continue to use WebClient with specific HttpHeaders like accept-language or user-agent. One of our QA guys uses NUnit to automate the IE WebServer Control, the manipulates the DHTML DOM to make Assertions.

Finally, my cleanup code is simple, deleting all the files we extracted as well as toasting the /bin folder.

[TestFixtureTearDown]

public void TearDown()

{

    try

    {

        if (webServer != null)

        {

            webServer.Stop();

            webServer = null;

        }

        CleanupResources();

        Directory.Delete(tempBinPath,true);

    }

    catch{}

}

 

private void CleanupResources()

{

    try

    {

        foreach(string file in extractedFilesToCleanup)

        {

            File.Delete(file);

        }

    }

    catch (Exception ex)

    {

        Debug.WriteLine(ex.ToString());

    }

}

Now the WebServer, Test Cases and Test are nicely self-contained and can be moved to the CruiseControl.NET Continuous Integration Build Server. 

Enjoy. I did.

Read: NUnit Unit Testing of ASP.NET Pages, Base Classes, Controls and other widgetry using Cassini...

Topic: Holiday gift idea Previous Topic   Next Topic Topic: Another solution to comment spam

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use