ServiceStack JsonServiceClient based test fails, b

2019-06-03 22:24发布

问题:

After I got my single-page web app working (web pages served with ServiceStack's RazorFormat() MVC, not .ASP MVC), I ran a (previously passing) test for the service. The test failed. Tested the web app again (debug run, navigate to //localhost:1337/ResourceList in the browser): still working. Is something wrong with my test?

Here's the error:

Test Name:  TestResourceList
Test FullName:  [0-1015]ServiceWrapper.Test.TestSWrapperServices.TestResourceList
Test Source:    c:\Users\uname\Documents\Visual Studio 2012\Projects\ServiceWrapper\UnitTestProject1\ServiceTests.cs : line 96
Test Outcome:   Failed
Test Duration:  0:00:02.188

Result Message: 
System.Net.WebException : Unable to connect to the remote server
  ----> System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it 127.0.0.1:1337
Result StackTrace:  
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](String httpMethod, String relativeOrAbsoluteUrl, Object request)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Get[TResponse](IReturn`1 request)
at ServiceWrapper.Test.TestSWrapperServices.TestResourceList() in c:\Users\uname\Documents\Visual Studio 2012\Projects\ServiceWrapper\UnitTestProject1\ServiceTests.cs:line 98
--SocketException
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)

Here's the test:

namespace ServiceWrapper.Test
{
    [TestFixture]
    public class TestSWrapperServices
    {
        AppHost appHost;
        private const string ListeningOn = "http://*:1337/";
        public const string Host = "http://localhost:1337";
        private const string BaseUri = Host + "/";

        [TestFixtureSetUp]
        public void OnTestFixtureSetUp()
        {
            var appSettings = new AppSettings();
            var username = Environment.GetEnvironmentVariable("USERNAME");
            var userdomain = Environment.GetEnvironmentVariable("USERDOMAIN");

            AppHost.AppConfig = new AppConfig(new AppSettings());

            appHost = new AppHost();

            // initialize Service Server
            ServiceServer.SetUser(AppHost.AppConfig.UserName, AppHost.AppConfig.Password);
            ServiceServer.SetLog(String.Empty);

            try
            {
                appHost.Init();
                appHost.Start(ListeningOn);
            }
            catch (HttpListenerException ex)
            {
                if (ex.ErrorCode == 5)
                {
                    System.Diagnostics.Debug.WriteLine("You need to run the following command (as admin):");
                    System.Diagnostics.Debug.WriteLine("  netsh http add urlacl url={0} user={1}\\{2} listen=yes",
                        ListeningOn, userdomain, username);
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
            }
        }

        [TestFixtureTearDown]
        public void OnTestFixtureTearDown()
        {
            appHost.Dispose();
        }

        [Test]
        public void TestResourceList()
        {
            JsonServiceClient client = new JsonServiceClient(BaseUri);
            ResourceList response = client.Get(new ResourceList());
            Assert.Contains("Some Value", response.property);
        }
        [Test]
    }
}

回答1:

I upgraded to the latest ServiceStack - 3.9.55, and it still didn't work. So, I started over again, sanity checking from the beginning. It turns out that the program.cs ListeningOn has http://*:1337/ while the nunit TestFixture ListeningOn was http://localhost:1337/

Checking urlacl (as admin) for http://localhost:1337/:

    C:\Windows\system32>netsh http show urlacl url=http://localhost:1337/

    URL Reservations:
    -----------------

Checking urlacl (as admin) for http://*:1337/:

    C:\Windows\system32>netsh http show urlacl url=http://*:1337/

    URL Reservations:
    -----------------

        Reserved URL            : http://*:1337/
            User: DOMAIN\user
                Listen: Yes
                Delegate: No
                SDDL: D:(A;;GX;;;S-1-5-21-2595267603-2801715271-1705165942-1002)

My earlier troubleshooting left the two projects with inconsistent ListeningOn values. Interestingly, using http://*:1337/ doesn't work as a wildcard url, as perhaps I had expected.

Here's a handy code snippet to help you build the add urlacl command. It also provides a useful (!) sanity check on the exact url you're listening on.

    Console.WriteLine("You need to run the following command:");
    Console.WriteLine("  netsh http add urlacl url={0} user={1}\\{2} listen=yes",
    ListeningOn, userdomain, username);

--- Update ---

Upgrading ServiceStack eliminated the 'connection actively refused' error message. Once ListeningOn values were unified, the real error message was exposed:

Result Message: ServiceStack.ServiceClient.Web.WebServiceException : Service Unavailable
Result StackTrace:  
at ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowWebServiceException[TResponse](Exception ex, String requestUri)
at ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowResponseTypeException[TResponse](Object request, Exception ex, String requestUri)
at ServiceStack.ServiceClient.Web.ServiceClientBase.HandleResponseException[TResponse](Exception ex, Object request, String requestUri, Func`1 createWebRequest, Func`2 getResponse, TResponse& response)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](String httpMethod, String relativeOrAbsoluteUrl, Object request)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Get[TResponse](IReturn`1 request)
at RemoteServerWrapper.Test.TestRSWrapperServices.TestDataList() in c:\Users\user\Documents\Visual Studio 2012\Projects\RemoteServerWrapper\UnitTestProject1\ServiceTests.cs:line 183

It's still obscure -- but at least it's not reporting something that's completely different from the real issue. So then I implemented trace in my app.config, like this:

<configuration>

  <!-- ... other config settings ... -->

  <system.diagnostics>
    <sources>
      <source name="System.Net" tracemode="includehex" maxdatasize="1024">
        <listeners>
          <add name="System.Net"/>
          <add name="console"/>
        </listeners>
      </source>
      <source name="System.Net.HttpListener">
        <listeners>
          <add name="System.Net"/>
          <add name="console"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
      <add name="System.Net.HttpListener" value="Verbose"/>
    </switches>
     <sharedListeners>
      <add name="console" 
        type="System.Diagnostics.ConsoleTraceListener" 
        initializeData="false"/>
      <add name="System.Net"
        type="System.Diagnostics.TextWriterTraceListener"
        initializeData="network.log"
      />
    </sharedListeners>
    <trace autoflush="true"/>
  </system.diagnostics>
</configuration>

Which exposed a better error message:

ERROR: [::1]:1337 Request not found: /datarequest?DataKey=some_key&startDate=20130701&endDate=20130708

OK - now I have to pull in the servicestack sources so I can step through the code and figure out why I'm getting 'Not Found' in the test, when it works when I 'debug/run' and test via the browser. Turns out that RestHandler.FindMatchingRestPath(httpMethod, pathInfo, contentType) wasn't returning a match. Humm. Why is that? The AppHost is declared identically. So, what's different?

The rest services live in my project's main assembly. When run from 'debug/run' the default assembly has the services, and everything works. But when run from the test project, with the services assembly added as a reference, servicestack can't find them. They're not in the default location, relative to the test project. So I added an AppHost class at the top of my test file, rather than relying on the one from my program.cs, and declared it as follows:

public class RSWrapperServicesAppHostHttpListener
            : AppHostHttpListenerBase
{
    public RSWrapperServicesAppHostHttpListener()
        : base("RSWrapper Services Tests", typeof(DataRequestService).Assembly) { } 

// 'DataRequestService' is a random rest service class, 
// defined in the referenced services assembly

}

Now ServiceStack is happy, and my tests work again.

How did they ever work? Originally everything was jumbled together all in one project. Once I separated things into separate assemblies, i.e. DTO, Services, Business Logic and Tests, I broke it. But since I was temporarily holding off on unit tests while getting the UI working, I didn't notice right away.