模拟WCF服务的简便方法?

时间:2009-01-02 14:45:32

标签: .net wcf unit-testing mocking

我有一个使用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,就不能这样做。

4 个答案:

答案 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。