如何在 ServiceStack 托管的测试中解决"HttpListenerBase.Instance has already been set"问题?



我有一个基于ServiceStack 3.9的项目构建,具有广泛的测试覆盖范围,使用ServiceStack的自托管API功能。

我的很多测试设备都是这样的:

private const string URL_BASE = "http://localhost:60666/";
[TestFixtureSetUp]
public void TestFixtureSetUp() {
    AppHost = new HttpTestableAppHost(FakeServices.Register);
    AppHost.Init();
    AppHost.Start(URL_BASE);
}
[TestFixtureTearDown]
public void TestFixtureTearDown() {
    AppHost.Dispose();
    AppHost = null;
}

问题是,如果测试失败,TearDown似乎并不总是干净地运行,然后项目上的其他所有测试都会以相同的错误失败,无论是

System.IO.InvalidDataException:HttpListenerBase.实例已设置为

或频率较低的

无法侦听前缀http://localhost:60666/,因为它与机器上的现有注册冲突

当这种情况发生时,整个测试套件将在几分钟内无法运行——可能是在某个底层网络绑定超时或被处理时?-几分钟后,一切又开始工作了。

我该如何更优雅地处理这个问题?有没有什么方法可以在初始化AppHost之前强制处理/注销http://localhost:60666/端点,这样它就会在启动新的服务主机之前杀死任何现有的服务主机?当一个"正确"的失败测试导致1000多个测试失败时,由于其他测试无法初始化其HTTP侦听器,因此越来越难弄清楚发生了什么。

卸载AppDomain:

干净地重新启动ServiceStack主机的唯一方法是卸载它正在运行的应用程序域,并在新的应用程序领域中启动新实例。

你的问题与这个问题有关&答复

1:创建您的AppHost(按照正常操作):

示例AppHost向IoC容器注册一个名为MyTest的对象。

public class AppHost : AppSelfHostBase
{
    public AppHost(): base("My ServiceStack Service", typeof(AppHost).Assembly)
    {
    }
    public override void Configure(Funq.Container container)
    {
        container.Register<MyTest>(c => new MyTest());
    }
}
public class MyTest
{
    public string Name { get { return "Hello"; } }
}

2:创建IsolatedAppHost类:

IsolatedAppHost类用于启动将在隔离的AppDomain中运行的应用程序主机。您可以在这里启动和配置您需要的任何AppHost,例如您的HttpTestableAppHost

public class IsolatedAppHost : MarshalByRefObject
{
    readonly AppHost Host;
    public IsolatedAppHost()
    {
        // Start your HttpTestableAppHost here
        Host = new AppHost();
        Host.Init();
        Host.Start("http://*:8090/");
        Console.WriteLine("ServiceStack is running in AppDomain '{0}'", AppDomain.CurrentDomain.FriendlyName);
    }
    public void RunTest(Action<AppHost> test)
    {
        test.Invoke(Host);
    }
    public void Teardown()
    {
        if(Host != null)
        {
            Console.WriteLine("Shutting down ServiceStack host");
            if(Host.HasStarted)
                Host.Stop();
            Host.Dispose();
        }
    }
}

3:创建您的TestFixture

您的TestFixureSetup方法需要在新的AppDomain中创建IsolatedAppHost的实例。TestFixtureTearDown将确保AppHost和域正确关闭。

[TestFixture]
public class Test
{
    AppDomain ServiceStackAppDomain;
    IsolatedAppHost IsolatedAppHost;
    [TestFixtureSetUp]
    public void TestFixtureSetup()
    {
        // Get the assembly of our host
        var assemblyName = typeof(IsolatedAppHost).Assembly.GetName();
        // Create new AppDomain
        ServiceStackAppDomain = AppDomain.CreateDomain("ServiceStackAppDomain");
        // Load our assembly
        ServiceStackAppDomain.Load(assemblyName);
        // Create instance
        var handle = ServiceStackAppDomain.CreateInstance(assemblyName.FullName, "MyApp.Tests.IsolatedAppHost");
        // Unwrap so we can access methods
        IsolatedAppHost = (IsolatedAppHost)handle.Unwrap();
    }
    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
        // Tell ServiceStack to stop the host
        IsolatedAppHost.Teardown();
        // Shutdown the ServiceStack application
        AppDomain.Unload(ServiceStackAppDomain);
        ServiceStackAppDomain = null;
    }
    // Tests go here
}

4:运行测试:

由于测试运行程序AppDomainAppHostAppDomain现在不同,我们无法直接从测试中访问AppHost。因此,我们将测试通过到我们的IsolatedAppHostRunTest方法实例。

IsolatedAppHost.RunTest(appHost => {
    // Your test goes here
});

例如:

[Test]
public void TestWillPass()
{
    IsolatedAppHost.RunTest(appHost => {
        var t = appHost.TryResolve<MyTest>();
        Assert.That(t.Name, Is.EqualTo("Hello"));
    });
}
[Test]
public void TestWillFail()
{
    IsolatedAppHost.RunTest(appHost => {
        var t = appHost.TryResolve<MyTest>();
        Assert.That(t.Name, Is.EqualTo("World"));
    });
}

完整的源代码在这里。我希望这能有所帮助。

相关内容

最新更新