我有一个使用WCF服务的应用。现在我想将单元测试添加到应用程序中。
在某些情况下,我需要模拟WCF服务,因为从服务中获取所需的行为有时很困难(例如服务抛出特殊异常)。
我可以为wcf客户端添加另一个接口,但这看起来有点傻,因为客户端调用已经在使用接口。
有没有简单的方法来模拟WCF服务?比创建另一个接口层并重定向其中的每个WCF调用更容易吗?
编辑:大多数答案似乎都不太了解使用WCF服务,所以澄清一下:
要从ViewModel使用WCF服务,我必须管理这样的连接:
ChannelFactory<IMyWcfService> channelFactory = new ChannelFactory<IMyWcfService>("");
IMyWcfService proxy = channelFactory.CreateChannel();
proxy.CallMyStuff();
proxy.Close();
我不能将ViewModel代理传递给WCF,因为需要为每个事务打开和关闭连接。因此,使用RhinoMock / NMock不起作用,因为它们需要一个ViewModel,它将代理作为参数,如果你使用WCF,就不能这样做。
答案 0 :(得分:9)
为什么不能使用NMock2之类的东西来直接模拟IMyWcfService
接口?
如果您需要能够动态创建新实例,请使用Factory隐藏客户端的ChannelFactory<IMyWcfService>
。通过这种方式,您可以替换工厂,为客户端提供一个创建模拟而不是真实代理的工具。
答案 1 :(得分:2)
您可以使用任何模拟框架(如RhinoMocks或NMock)来模拟接口契约,因此如果您的服务实现了IMyService,那么您可以使用模拟框架来设置对该接口上的方法调用的期望。如果您不熟悉这个概念,那么您可以简单地创建一个实现IMyService的替身对象,但在测试期间假装是真正的服务。这样,当调用这些方法时,它们会在您的替身上被调用,您可以根据需要随意返回。
答案 2 :(得分:0)
你可以Moq
嘲笑框架。根据您提供的示例:
ChannelFactory<IMyWcfService> channelFactory = new ChannelFactory<IMyWcfService>("");
IMyWcfService proxy = channelFactory.CreateChannel();
proxy.CallMyStuff();
proxy.Close();
以下是模拟实现的外观:
Mock<IMyWcfServiceChannel> channelMock = new Mock<IMyWcfServiceChannel>(MockBehavior.Strict);
channelMock
.Setup(c => c.CallMyStuff())
.Returns("");
string myStuff = channelMock.Object.CallMyStuff();
为WCF
服务添加代理后,您应该可以使用channel
界面,称为IMyWcfServiceChannel
。
根据您调用的服务方法的返回类型 - 您可以设置任何输出。在上面的示例中,我使用string
类型作为示例。
为了更有效地使用上述解决方案,您可能需要为业务层创建2个构造函数,如下所示:
public class Example1
{
IMyWcfServiceChannel _client;
public Example1()
{
var factory = new ChannelFactory<IMyWcfServiceChannel>("binding");
_client = factory.CreateChannel();
}
public Example1(IMyWcfServiceChannel client)
{
_client = client;
}
public string CallMyStuff()
{
return _client.CallMyStuff();
}
}
所以在prod
上使用无参数构造函数。在unit
测试中,您使用参数完整构造函数并将模型传递给它(channelMock.Object
)。
答案 3 :(得分:0)
我正在使用 FakeItEasy,它不允许模拟 OperationContext,因为它是一个密封类。将依赖注入添加到组合中,您将成为一场直接的噩梦。我花了一个星期试图弄清楚这些东西,这就是我最终想出的。 . .您将创建几个 ServiceHost
,然后它们将相互通信并在其间运行您的所有代码。
#1 创建一个继承自 ClientBase<IMyWcfService>
的类:
public class MyWcfServiceClient : ClientBase<IMyWcfService>, IMyWcfService
{
public MyWcfServiceClient(string address)
: base(new WebHttpBinding(), new EndpointAddress(address))
{
this.Endpoint.EndpointBehaviors.Add(new WebHttpBehavior());
}
public void CallMyStuff()
{
using (new OperationContextScope(this.InnerChannel))
{
base.Channel.CallMyStuff();
}
}
}
#2 创建一个将调用此方法的“调用服务”。首先,创建一个接口:
[ServiceContract]
public interface IMyWcfCallingService
{
[OperationContract]
void CallCallMyStuff();
}
然后创建实现这个接口的“调用服务”:
public class MyWcfCallingService : IMyWcfCallingService
{
static MyWcfServiceClient _client = new MyWcfServiceClient("http://localhost:8008");
// ^^^ This "http://localhost:8008" is the address where
// your actual service is going to "live" in your unit test
public void CallCallMyStuff()
{
_client.CallMyStuff();
}
}
#3 在单元测试中实例化你的实际服务:
var myService = new MyWcfService(_someMockedDependency, _someOtherMockedDependency);
#4 创建两个要相互通信的 ServiceHost
,然后调用您的服务:
var restHost = new WebServiceHost(myService, new Uri("http://localhost:8008"));
// ^^^ Make sure the URL here matches the URL you used in Step #2
var behavior = restHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
behavior.InstanceContextMode = InstanceContextMode.Single;
// ^^^ If you're using dependency injection with mocked dependencies
// to create your myService object, this is muy importante
restHost.Open();
var clientHost = new ServiceHost(typeof(MyWcfCallingService), new Uri("http://localhost:80"));
// ^^^ use some other address here, different from the one you used for the service itself
clientHost.AddServiceEndpoint(typeof(IMyWcfCallingService), new BasicHttpBinding(), string.Empty);
clientHost.Open();
var factory = new ChannelFactory<IMyWcfCallingService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:80"));
// ^^^ this should match the url for the clienthost
var proxy = factory.CreateChannel();
proxy.CallCallMyStuff();
该死的 WCF 见鬼去吧!我从来没有像开始挖掘遗留 WCF 代码时那样欣赏 WebApi。