如何集成测试 SoapCore 端点?
How do I integration test a SoapCore endpoint?
我在 .net core 3.1 应用程序中使用 SoapCore,并且我已经为使用 Web 应用程序工厂的控制器设置了集成测试策略,例如
internal class IntegrationTestApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
public HttpClient CreateConfiguredHttpClient()
{
var client = this.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
return client;
}
}
这适用于我的控制器集成测试,如下所示:
[Fact]
public async Task OrderDelivery_NoRequestBody_ReturnsBadRequest()
{
var xml = ...
var response = await _client
.PostAsync("http://localhost/api/myController", new StringContent(xml));;
var responseBody = await response.Content.ReadAsStringAsync();
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
responseBody.Should().Contain("Invalid XML");
}
但我现在正在尝试测试一些使用 SoapCore 的 soap 端点。这是在启动文件中:
public static IApplicationBuilder AddSoapCoreEndpoints(this IApplicationBuilder app, IConfiguration
config, IHostEnvironment env)
{
var settings = config.GetSection("FileWSDL").Get<WsdlFileOptions>();
settings.AppPath = env.ContentRootPath;
app.Map("/service", x =>
{
x.UseSoapEndpoint<IService>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.XmlSerializer, false, null, settings);
});
}
wsdl 文件是预先生成的。
然后在我的测试项目中,我通过浏览到 wsdl 文件添加了一个连接的服务,并编写了这个测试主体:
var endpoint = new EndpointAddress("http://localhost/service/MyService.svc");
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
var client = new MyServiceClient(binding, endpoint);
var response = await client.DoSomething();
我得到这个异常:
System.ServiceModel.EndpointNotFoundException: 'There was no endpoint listening at http://localhost/service/MyService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.'
没有内部异常。
有趣的是,虽然这通过使用我的控制器测试使用的相同客户端给了我 200:
await _client.GetAsync("http://localhost/service/MyService.svc");
查看 Connected Services > MyService > Reference 我可以看到有一个基于引用的端口,这有点令人担忧,但我相信我在测试主体中指定的新端点应该意味着不会使用过。
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IMyService))
{
return new System.ServiceModel.EndpointAddress("https://localhost:44399/service/MyService.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
基于@Craig (OP) 回答中的代码的更新,但适用于您没有生成的 WCF 客户端的情况。此答案中进一步提供了具有完整设置的 IMySoapSvc
的代码。
using System;
using System.ServiceModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using MyMicroservice;
using MyMicroservice.SoapSvc;
using Xunit;
namespace XUnitTestProject.Craig
{
public class WcfWebApplicationFactoryTest : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public WcfTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task sayHello_normalCond_receive_HelloWorld()
{
await Task.Delay(1); // because of some issues with WebApplicationFactory, test method needs to be async
var endpoint = new EndpointAddress(new Uri("http://localhost/MyService.svc"));
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
using var channelFactory = new ChannelFactory<IMySoapSvc>(binding, endpoint);
// entry point for code from @Craig
channelFactory.Endpoint.InterceptRequestsWithHttpClient(_factory.CreateClient());
var wcfClient = channelFactory.CreateChannel();
var response = wcfClient.SayHello();
Assert.Equal("Hello world", response);
}
}
}
使用 SOAP 客户端的替代方法是使用常规 POST 请求。
下面是支持 SOAP 1.1 和 1.2 的简单 Hello World SOAP 服务的分步说明。最后,有几个测试使用 WebApplicationFactory
,然后使用 ChannelFactory
.
添加此 Nuget(或可用时更新)
<PackageReference Include="SoapCore" Version="1.1.0.7" />
SOAP 服务
using System.ServiceModel;
namespace MyMicroservice.SoapSvc
{
[ServiceContract(Name = "MySoapSvc", Namespace = "http://www.mycompany.no/mysoap/")]
public interface IMySoapSvc
{
[OperationContract(Name = "sayHello")]
string SayHello();
}
public class MySoapSvc : IMySoapSvc
{
public string SayHello()
{
return "Hello world";
}
}
}
启动#ConfigureServices
using var iisUrlRewriteStreamReader = File.OpenText("RewriteRules.xml");
var options = new RewriteOptions()
.AddIISUrlRewrite(iisUrlRewriteStreamReader);
app.UseRewriter(options);
services.AddSingleton<IMySoapSvc, MySoapSvc>();
启动#配置
var soap12Binding = new CustomBinding(new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressingAugust2004, System.Text.Encoding.UTF8),
new HttpTransportBindingElement());
app.UseSoapEndpoint<IMySoapSvc>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.XmlSerializer);
app.UseSoapEndpoint<IMySoapSvc>("/MyService12.svc", soap12Binding, SoapSerializer.XmlSerializer);
重写规则以在 SOAP 1.1/1.2 之间拆分。将其放入文件 RewriteRules.xml 与 Startup.cs.
相同的文件夹中
<rewrite>
<rules>
<rule name="Soap12" stopProcessing="true">
<match url="(.*)\.svc" />
<conditions>
<add input="{REQUEST_METHOD}" pattern="^POST$" />
<add input="{CONTENT_TYPE}" pattern=".*application/soap\+xml.*" />
</conditions>
<action type="Rewrite" url="/{R:1}12.svc" appendQueryString="false" />
</rule>
</rules>
</rewrite>
您的项目文件中需要这个 RewriteRules.xml
<ItemGroup>
<None Update="RewriteRules.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
最后是测试。在这里我们可以看到 SOAP 1.1 和 SOAP 1.2 请求之间的详细差异。
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using MyMicroservice;
using Xunit;
namespace XUnitTestProject
{
public class BasicTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public BasicTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/MyService.svc")]
public async Task helloWorld_validEnvelope11_receiveOk(string url) {
const string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body>
<sayHello xmlns=""http://www.mycompany.no/mysoap/""></sayHello>
</soap:Body>
</soap:Envelope>";
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("SOAPAction", "http://localhost/mysoap/sayHello");
var response = await client
.PostAsync(url, new StringContent(envelope, Encoding.UTF8, "text/xml"));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("Hello world", await response.Content.ReadAsStringAsync());
}
[Theory]
[InlineData("/MyService.svc")]
public async Task helloWorld_validEnvelope12_receiveOk(string url)
{
const string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
<soap12:Body >
<sayHello xmlns=""http://www.mycompany.no/mysoap/""></sayHello>
</soap12:Body>
</soap12:Envelope>";
var client = _factory.CreateClient();
var response = await client
.PostAsync(url, new StringContent(envelope, Encoding.UTF8, "application/soap+xml"));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("Hello world", await response.Content.ReadAsStringAsync());
}
}
}
另一种方法是使用 ChannelFactory
在端口 5000 上获取具有普通主机 运行 的客户端。为此,我们需要在测试库中启动 Web 环境 class。这种方法的运行速度明显快于 WebApplicationFactory
。请参阅此答案末尾的屏幕截图。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using MyMicroservice;
using Xunit;
namespace XUnitTestProject
{
public class HostFixture : IAsyncLifetime
{
private IHost _host;
public async Task InitializeAsync()
{
_host = CreateHostBuilder().Build();
await _host.StartAsync();
}
public async Task DisposeAsync()
{
await _host.StopAsync();
_host.Dispose();
}
private static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
然后是测试class。这是非常标准的代码。
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using MyMicroservice.SoapSvc;
using Xunit;
namespace XUnitTestProject
{
public class WcfTest : IClassFixture<HostFixture>
{
[Theory]
[InlineData("http://localhost:5000/MyService.svc")]
public void sayHello_normalCond_receiveHelloWorld11(string url)
{
var binding = new BasicHttpBinding();
var endpoint = new EndpointAddress(new Uri(url));
using var channelFactory = new ChannelFactory<IMySoapSvc>(binding, endpoint);
var serviceClient = channelFactory.CreateChannel();
var response = serviceClient.SayHello();
Assert.Equal("Hello world", response);
}
[Theory]
[InlineData("http://localhost:5000/MyService.svc")]
public void sayHello_normalCond_receiveHelloWorld12(string url)
{
var soap12Binding = new CustomBinding(
new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressingAugust2004, System.Text.Encoding.UTF8),
new HttpTransportBindingElement());
var endpoint = new EndpointAddress(new Uri(url));
using var channelFactory = new ChannelFactory<IMySoapSvc>(soap12Binding, endpoint);
var serviceClient = channelFactory.CreateChannel();
var response = serviceClient.SayHello();
Assert.Equal("Hello world", response);
}
}
}
显示测试经过时间的屏幕截图。获胜者是 WcfTest
在端口 5000 上使用主机 运行,第二名是 BasicTests
使用 WebApplicationFactory
和普通 POST 请求。
更新:由于设置时间更短,使用 NUnit 和 WebApplicationFactory
(此处未显示)的测试运行速度提高了 4 倍。
使用 .NET Core 5 测试。
我通过使用通过问题中的 WebApplicationFactory 创建的客户端解决了这个问题 - 因为内存中的集成测试 运行 和 WCF 客户端根据 fiddler 创建了真实的网络请求,这显然不会工作(没有服务器接受实际的网络请求),以及从这些 github 问题中提取并略微修改的代码:
https://github.com/dotnet/wcf/issues/2400
https://github.com/dotnet/wcf/issues/4214
我添加了 3 个新的 classes:
新行为
internal class HttpMessageHandlerBehavior : IEndpointBehavior
{
private readonly HttpClient _client;
public HttpMessageHandlerBehavior(HttpClient _client)
{
this._client = _client;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(GetHttpMessageHandler));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
public HttpMessageHandler GetHttpMessageHandler(HttpClientHandler httpClientHandler)
{
return new InterceptingHttpMessageHandler(httpClientHandler, _client);
}
public Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> OnSendingAsync { get; set; }
public Func<HttpResponseMessage, CancellationToken, Task<HttpResponseMessage>> OnSentAsync { get; set; }
}
拦截上述 class 使用的请求的处理程序
internal class InterceptingHttpMessageHandler : DelegatingHandler
{
private readonly HttpClient _client;
public InterceptingHttpMessageHandler(HttpMessageHandler innerHandler, HttpClient client)
{
InnerHandler = innerHandler;
_client = client;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await _client.PostAsync(request.RequestUri, request.Content, cancellationToken);
}
}
以及抽象所有这些的扩展方法(注意上面的内部关键字)
public static class ServiceEndpointExtensions {
public static void InterceptRequestsWithHttpClient(this ServiceEndpoint endpoint, HttpClient httpClient)
{
endpoint.EndpointBehaviors.Add(new HttpMessageHandlerBehavior(httpClient));
}
}
用法:
private readonly DownloadWebServiceClient _sut;
public DownloadWebServiceTests()
{
var endpoint = new EndpointAddress(TestConstants.Root + "/service/download.svc");
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
var client = new DownloadWebServiceClient(binding, endpoint);
client.Endpoint.InterceptRequestsWithHttpClient(_client);
_sut = client;
}
[Fact]
public async Task GetDocument_Unauthenticated_Throws()
{
var request = new DownloadRequest
{
Authentication = new Authentication
{
Username = "hello",
Password = "good bye"
},
DocumentId = 1
};
_sut.Invoking(async s => await s.GetDocumentAsync(request))
.Should()
.Throw<FaultException>();
}
我在 .net core 3.1 应用程序中使用 SoapCore,并且我已经为使用 Web 应用程序工厂的控制器设置了集成测试策略,例如
internal class IntegrationTestApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
public HttpClient CreateConfiguredHttpClient()
{
var client = this.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
return client;
}
}
这适用于我的控制器集成测试,如下所示:
[Fact]
public async Task OrderDelivery_NoRequestBody_ReturnsBadRequest()
{
var xml = ...
var response = await _client
.PostAsync("http://localhost/api/myController", new StringContent(xml));;
var responseBody = await response.Content.ReadAsStringAsync();
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
responseBody.Should().Contain("Invalid XML");
}
但我现在正在尝试测试一些使用 SoapCore 的 soap 端点。这是在启动文件中:
public static IApplicationBuilder AddSoapCoreEndpoints(this IApplicationBuilder app, IConfiguration
config, IHostEnvironment env)
{
var settings = config.GetSection("FileWSDL").Get<WsdlFileOptions>();
settings.AppPath = env.ContentRootPath;
app.Map("/service", x =>
{
x.UseSoapEndpoint<IService>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.XmlSerializer, false, null, settings);
});
}
wsdl 文件是预先生成的。
然后在我的测试项目中,我通过浏览到 wsdl 文件添加了一个连接的服务,并编写了这个测试主体:
var endpoint = new EndpointAddress("http://localhost/service/MyService.svc");
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
var client = new MyServiceClient(binding, endpoint);
var response = await client.DoSomething();
我得到这个异常:
System.ServiceModel.EndpointNotFoundException: 'There was no endpoint listening at http://localhost/service/MyService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.'
没有内部异常。
有趣的是,虽然这通过使用我的控制器测试使用的相同客户端给了我 200:
await _client.GetAsync("http://localhost/service/MyService.svc");
查看 Connected Services > MyService > Reference 我可以看到有一个基于引用的端口,这有点令人担忧,但我相信我在测试主体中指定的新端点应该意味着不会使用过。
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IMyService))
{
return new System.ServiceModel.EndpointAddress("https://localhost:44399/service/MyService.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
基于@Craig (OP) 回答中的代码的更新,但适用于您没有生成的 WCF 客户端的情况。此答案中进一步提供了具有完整设置的 IMySoapSvc
的代码。
using System;
using System.ServiceModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using MyMicroservice;
using MyMicroservice.SoapSvc;
using Xunit;
namespace XUnitTestProject.Craig
{
public class WcfWebApplicationFactoryTest : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public WcfTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task sayHello_normalCond_receive_HelloWorld()
{
await Task.Delay(1); // because of some issues with WebApplicationFactory, test method needs to be async
var endpoint = new EndpointAddress(new Uri("http://localhost/MyService.svc"));
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
using var channelFactory = new ChannelFactory<IMySoapSvc>(binding, endpoint);
// entry point for code from @Craig
channelFactory.Endpoint.InterceptRequestsWithHttpClient(_factory.CreateClient());
var wcfClient = channelFactory.CreateChannel();
var response = wcfClient.SayHello();
Assert.Equal("Hello world", response);
}
}
}
使用 SOAP 客户端的替代方法是使用常规 POST 请求。
下面是支持 SOAP 1.1 和 1.2 的简单 Hello World SOAP 服务的分步说明。最后,有几个测试使用 WebApplicationFactory
,然后使用 ChannelFactory
.
添加此 Nuget(或可用时更新)
<PackageReference Include="SoapCore" Version="1.1.0.7" />
SOAP 服务
using System.ServiceModel;
namespace MyMicroservice.SoapSvc
{
[ServiceContract(Name = "MySoapSvc", Namespace = "http://www.mycompany.no/mysoap/")]
public interface IMySoapSvc
{
[OperationContract(Name = "sayHello")]
string SayHello();
}
public class MySoapSvc : IMySoapSvc
{
public string SayHello()
{
return "Hello world";
}
}
}
启动#ConfigureServices
using var iisUrlRewriteStreamReader = File.OpenText("RewriteRules.xml");
var options = new RewriteOptions()
.AddIISUrlRewrite(iisUrlRewriteStreamReader);
app.UseRewriter(options);
services.AddSingleton<IMySoapSvc, MySoapSvc>();
启动#配置
var soap12Binding = new CustomBinding(new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressingAugust2004, System.Text.Encoding.UTF8),
new HttpTransportBindingElement());
app.UseSoapEndpoint<IMySoapSvc>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.XmlSerializer);
app.UseSoapEndpoint<IMySoapSvc>("/MyService12.svc", soap12Binding, SoapSerializer.XmlSerializer);
重写规则以在 SOAP 1.1/1.2 之间拆分。将其放入文件 RewriteRules.xml 与 Startup.cs.
相同的文件夹中<rewrite>
<rules>
<rule name="Soap12" stopProcessing="true">
<match url="(.*)\.svc" />
<conditions>
<add input="{REQUEST_METHOD}" pattern="^POST$" />
<add input="{CONTENT_TYPE}" pattern=".*application/soap\+xml.*" />
</conditions>
<action type="Rewrite" url="/{R:1}12.svc" appendQueryString="false" />
</rule>
</rules>
</rewrite>
您的项目文件中需要这个 RewriteRules.xml
<ItemGroup>
<None Update="RewriteRules.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
最后是测试。在这里我们可以看到 SOAP 1.1 和 SOAP 1.2 请求之间的详细差异。
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using MyMicroservice;
using Xunit;
namespace XUnitTestProject
{
public class BasicTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public BasicTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/MyService.svc")]
public async Task helloWorld_validEnvelope11_receiveOk(string url) {
const string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body>
<sayHello xmlns=""http://www.mycompany.no/mysoap/""></sayHello>
</soap:Body>
</soap:Envelope>";
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("SOAPAction", "http://localhost/mysoap/sayHello");
var response = await client
.PostAsync(url, new StringContent(envelope, Encoding.UTF8, "text/xml"));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("Hello world", await response.Content.ReadAsStringAsync());
}
[Theory]
[InlineData("/MyService.svc")]
public async Task helloWorld_validEnvelope12_receiveOk(string url)
{
const string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
<soap12:Body >
<sayHello xmlns=""http://www.mycompany.no/mysoap/""></sayHello>
</soap12:Body>
</soap12:Envelope>";
var client = _factory.CreateClient();
var response = await client
.PostAsync(url, new StringContent(envelope, Encoding.UTF8, "application/soap+xml"));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("Hello world", await response.Content.ReadAsStringAsync());
}
}
}
另一种方法是使用 ChannelFactory
在端口 5000 上获取具有普通主机 运行 的客户端。为此,我们需要在测试库中启动 Web 环境 class。这种方法的运行速度明显快于 WebApplicationFactory
。请参阅此答案末尾的屏幕截图。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using MyMicroservice;
using Xunit;
namespace XUnitTestProject
{
public class HostFixture : IAsyncLifetime
{
private IHost _host;
public async Task InitializeAsync()
{
_host = CreateHostBuilder().Build();
await _host.StartAsync();
}
public async Task DisposeAsync()
{
await _host.StopAsync();
_host.Dispose();
}
private static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
然后是测试class。这是非常标准的代码。
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using MyMicroservice.SoapSvc;
using Xunit;
namespace XUnitTestProject
{
public class WcfTest : IClassFixture<HostFixture>
{
[Theory]
[InlineData("http://localhost:5000/MyService.svc")]
public void sayHello_normalCond_receiveHelloWorld11(string url)
{
var binding = new BasicHttpBinding();
var endpoint = new EndpointAddress(new Uri(url));
using var channelFactory = new ChannelFactory<IMySoapSvc>(binding, endpoint);
var serviceClient = channelFactory.CreateChannel();
var response = serviceClient.SayHello();
Assert.Equal("Hello world", response);
}
[Theory]
[InlineData("http://localhost:5000/MyService.svc")]
public void sayHello_normalCond_receiveHelloWorld12(string url)
{
var soap12Binding = new CustomBinding(
new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressingAugust2004, System.Text.Encoding.UTF8),
new HttpTransportBindingElement());
var endpoint = new EndpointAddress(new Uri(url));
using var channelFactory = new ChannelFactory<IMySoapSvc>(soap12Binding, endpoint);
var serviceClient = channelFactory.CreateChannel();
var response = serviceClient.SayHello();
Assert.Equal("Hello world", response);
}
}
}
显示测试经过时间的屏幕截图。获胜者是 WcfTest
在端口 5000 上使用主机 运行,第二名是 BasicTests
使用 WebApplicationFactory
和普通 POST 请求。
更新:由于设置时间更短,使用 NUnit 和 WebApplicationFactory
(此处未显示)的测试运行速度提高了 4 倍。
使用 .NET Core 5 测试。
我通过使用通过问题中的 WebApplicationFactory 创建的客户端解决了这个问题 - 因为内存中的集成测试 运行 和 WCF 客户端根据 fiddler 创建了真实的网络请求,这显然不会工作(没有服务器接受实际的网络请求),以及从这些 github 问题中提取并略微修改的代码:
https://github.com/dotnet/wcf/issues/2400
https://github.com/dotnet/wcf/issues/4214
我添加了 3 个新的 classes:
新行为
internal class HttpMessageHandlerBehavior : IEndpointBehavior
{
private readonly HttpClient _client;
public HttpMessageHandlerBehavior(HttpClient _client)
{
this._client = _client;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(GetHttpMessageHandler));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
public HttpMessageHandler GetHttpMessageHandler(HttpClientHandler httpClientHandler)
{
return new InterceptingHttpMessageHandler(httpClientHandler, _client);
}
public Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> OnSendingAsync { get; set; }
public Func<HttpResponseMessage, CancellationToken, Task<HttpResponseMessage>> OnSentAsync { get; set; }
}
拦截上述 class 使用的请求的处理程序
internal class InterceptingHttpMessageHandler : DelegatingHandler
{
private readonly HttpClient _client;
public InterceptingHttpMessageHandler(HttpMessageHandler innerHandler, HttpClient client)
{
InnerHandler = innerHandler;
_client = client;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return await _client.PostAsync(request.RequestUri, request.Content, cancellationToken);
}
}
以及抽象所有这些的扩展方法(注意上面的内部关键字)
public static class ServiceEndpointExtensions {
public static void InterceptRequestsWithHttpClient(this ServiceEndpoint endpoint, HttpClient httpClient)
{
endpoint.EndpointBehaviors.Add(new HttpMessageHandlerBehavior(httpClient));
}
}
用法:
private readonly DownloadWebServiceClient _sut;
public DownloadWebServiceTests()
{
var endpoint = new EndpointAddress(TestConstants.Root + "/service/download.svc");
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
var client = new DownloadWebServiceClient(binding, endpoint);
client.Endpoint.InterceptRequestsWithHttpClient(_client);
_sut = client;
}
[Fact]
public async Task GetDocument_Unauthenticated_Throws()
{
var request = new DownloadRequest
{
Authentication = new Authentication
{
Username = "hello",
Password = "good bye"
},
DocumentId = 1
};
_sut.Invoking(async s => await s.GetDocumentAsync(request))
.Should()
.Throw<FaultException>();
}