Unit Testing ASP.NET/Client Application Service / Programmatically Starting Web Development Server (WebDev.WebServer.exe)

What is seemingly a very simple process turned into quite the adventure - a costly one in terms of following dead-ends and beating my head against the wall - I first started with the programmatically started service (failing miserably) and evolved into the attribute driven approach which I finally got to work but it had constraints.....

I will detail how to make both methods work, highlighting areas left out of available blogs and research material that will result in errors that yield no answers on the internet.  

The adventure started with an annoying problem with my EHR unit test - I have a number of test that test the Client Application Service (for security) and if I don't manually start up the ApplicationService web service they will fail - this morning I rolled up my sleeves and decided to automate this process so at any time I can run all test in solution and have the service automatically start for the test that depend on it.

Below shows the syntax for the StartWebApplication() TestContext extension method that does the job of starting up the service during test initialize programmatically. 

Before going into more detail the following source demonstrates how to use the attribute method for starting the ApplicationService:

THE KEY to using attributes is to first ensure you have the namespace available - you'll find many resources that will correctly inform you that you must use the following:

using Microsoft.VisualStudio.TestTools.UnitTesting.Web;

However, what is not easily found is the DLL that this unit test resides in!!!   After a long search and destroy mission (online failed) I started searching the Microsoft.VisualStudio DLLs and found it in the Microsoft.VisualStudio.QualityTools.WebTestFramework 

A second piece of information that can really eat your lunch if it escapes you is that you MUST have a DEFAULT.ASPX file in the service!!!  Since I was using a Client Application Service (which doesn't even have a service, never mind a default.aspx file) I ran into the errors you'll see below that affected both the programmatic method as well as the attribute method.   Ensure you have a Default.aspx file.

CONSTRAINTS

I could be missing something but I could not get a breakpoint to work on the unit test that had this attribute - this was unacceptable but at least I got the web development server to start automajically (for the first time) so I was going to make it work.   I decided to have an initialize (empty) service that would start the service so that it was available for the rest of the unit test.

I then ran into a problem - you cannot control the order of your test (outside of an ordered test).  I want to be able to "run all test in solution" and have it work (which it currently does).   I saw blogs that tell you that you can use the Priority attribute but it doesn't work - later this was confirmed on the MSDN site that holds the information for this attribute; it clearly states that this is not used by the test engine and is only for developer use.

My work-around; I found that the same test always is the first test to run so I put the attribute on it :)  Ugly work-around but it works.   You'll find it on my CanGetCloudContainer() unit test which is always the first test to run in the solution.   Later I was able to get the programmatic approach to work consistently (by adding a default.aspx file) so I really don't need this solution but keep it in there as a code reference (for this blog).   I did code the TestContextExtension so that if a WebDev.Server is available it will not attempt to programmatically load it.   This supports both "all unit test in solution" (attribute) and the programmatic approach (if running only that test fixture during development).

HEAD-BANGERS

ERROR:  The ASP.NET Web application at 'C:\_\_EHR\Layers\Service\GWN.EHR.ApplicationService' is already configured for testing by another test run. Only one test run at a time can run tests in ASP.NET. If there are no other test runs using this Web application, ensure that the Web.config file does not contain an httpModule named HostAdapter.

This error occurs if you are using the attribute method and you crashed out of the unit test.   Behind the scenes when the test starts the Web.Config is modified; the HostAdapter is added to it.  When the test are all completed it is removed.  If you crash out of the test and this is not removed then you will see the above error.   To watch this at work simply load your Web.Config of your service and run the test - you will be notified when the Web.Config file is changed (first to add and the second to remove the HostAdapter statement).

ERROR:  The Web request 'http://localhost:2035/AppService' completed successfully without running the test. This can occur when configuring the Web application for testing fails (an ASP.NET server error occurs when processing the request), or when no ASP.NET page is executed (the URL may point to an HTML page, a Web service, or a directory listing). Running tests in ASP.NET requires the URL to resolve to an ASP.NET page and for the page to execute properly up to the Load event. The response from the request is stored in the file 'WebRequestResponse_CanGetCloudBlobConta.html' with the test results; typically this file can be opened with a Web browser to view its contents.

The above error occurs if the the web application/service does not have a Default.aspx file.   If you debug the test you'll see the ASP.NET Web Development Server load, the test will fail and then the Web Development server will close.

With the Default.aspx file missing you can see the "in progress" test below fails for a "Configuration Error".   At other times I could get the programmatic approach to work without the default.aspx however I will ensure I have one to ensure I don't stumble upon the following error again:

 

The error for CanGetCloudBlobContainer (attribute load of Web Development Server) above had the following "The web site could not be configured correctly" error also: 

Source code for TestContext extension that loads the WebDev.WebServer.exe follows:

using System.Diagnostics;

using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

 

namespace GWN.Library.Tests.Extensions

{

    public static class TestContextExtension

    {

        private static Process _currentProcess;

 

        public const string WebDevWebServerExe =
            @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.exe";

 

        public static string StartWebApplication(this TestContext testContext,

            string appName, string port, string fullAppPath)

        {

            if (_currentProcess != null)

                return null;

 

            // If other processes, i.e., attribute process, loaded webserver

            // then don't run this process (it is already available).  We only

            // run one service at this point so we'll worry about multiple ones

            // when the need arises..

            if (Process.GetProcesses()

                .Any(process => process.ProcessName.Contains("WebDev.WebServer")))

                    return null;

 

            var fullWebPath = string

                .Format("/port:{0} /path:\"{1}{2}\" /vpath:\"/{3}\"",

                       port,

                       testContext.GetRootPath(),

                       fullAppPath,

                       appName);

 

            var startinfo = new ProcessStartInfo(WebDevWebServerExe, fullWebPath)

                {

                    WindowStyle = ProcessWindowStyle.Hidden,

                };

 

            _currentProcess = Process.Start(startinfo);

 

            return fullWebPath;

        }

 

        public static string GetRootPath(this TestContext testContext)

        {

            var offset = testContext.TestDir.IndexOf("TestResults");

            var path = testContext.TestDir.Substring(0, offset);

            return path;

        }

    }

}

ATTRIBUTE APPROACH (full source since image is cut off)

/// <summary>

/// Determines whether this instance [can get BLOB container].

/// </summary>

[TestMethod]

[HostType("ASP.NET")]

[AspNetDevelopmentServerHost(
   @"C:\_\_EHR\Layers\Service\GWN.EHR.ApplicationService", "/AppService")]

[UrlToTest("http://localhost:2035/AppService")]

public void CanGetCloudBlobContainer()

{

    // Get list

    var blobList = client.ReadContainerList();

 

    // Get first container

    var blobContainer = blobList.FirstOrDefault();

 

    Assert.IsNotNull(blobContainer);

 

    // Use first container (name) to get container from list

    var cloudBlobContainer = client.ReadContainer(blobContainer.Name);

 

    // Assert the names match

    Assert.AreEqual(blobContainer.Name, cloudBlobContainer.Name);

}


Tags: , ,
Categories:


Actions: E-mail | Permalink |  Grammar/Typo/Better way? Please let me know