Простой способ издеваться над сервисом WCF?

У меня есть приложение, которое использует сервис WCF. Теперь я хотел бы добавить модульные тесты в приложение.

В некоторых случаях мне нужно смоделировать службу WCF, поскольку иногда получить жесткое поведение от службы сложно (например, служба генерирует особые исключения).

Я мог бы добавить еще один интерфейс к клиенту wcf, но это кажется немного глупым, поскольку клиентские вызовы уже используют интерфейс.

Есть ли простой способ издеваться над сервисом WCF? Легче, чем создать еще один интерфейсный слой и перенаправить каждый вызов WCF внутри него?

Изменить: Большинство ответов, кажется, не знают много об использовании службы WCF, поэтому некоторые пояснения:
Чтобы использовать службу WCF из ViewModel, мне нужно управлять соединением примерно так:

ChannelFactory<IMyWcfService> channelFactory = new ChannelFactory<IMyWcfService>("");
IMyWcfService proxy = channelFactory.CreateChannel();
proxy.CallMyStuff();
proxy.Close();

Я не могу просто передать ViewModel прокси в WCF, так как соединение должно быть открыто и закрыто для каждой транзакции. По этой причине использование RhinoMock/NMock не будет работать, так как им нужен ViewModel, который получает прокси в качестве параметра, что не может быть сделано, если вы используете WCF.

4 ответа

Решение

Почему вы не можете использовать что-то вроде NMock2, чтобы высмеивать IMyWcfService интерфейсы напрямую?

Если вам нужно создавать новые экземпляры на лету, используйте Factory, чтобы скрыть ChannelFactory<IMyWcfService> от клиента. Таким образом, вы можете заменить фабрику, предоставив клиентскую, которая создает фиктивные файлы вместо реальных прокси.

Ты можешь ты 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).

Вы можете использовать любую среду моделирования, такую ​​как RhinoMocks или NMock, чтобы смоделировать контракт интерфейса, поэтому, если ваша служба реализует IMyService, вы можете использовать среду моделирования, чтобы установить ожидания вызовов методов для этого интерфейса. Если вы не знакомы с этой концепцией, вы можете просто создать отдельный объект, который реализует IMyService, но при тестировании выдает себя за реальную службу. Таким образом, когда вызываются методы, они вызываются для вашего резервного объекта, и вы можете получить свой возврат в любом месте, куда захотите.

Я использую FakeItEasy, который не позволяет насмехаться над OperationContext, потому что это закрытый класс. Добавьте в смесь инъекцию зависимостей, и вы получите настоящий кошмар. Я потратил неделю, пытаясь разобраться в этом, и вот что я наконец придумал. . . вы собираетесь создать пару, а затем они будут разговаривать друг с другом и запускать весь ваш код между ними.

# 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, все к черту! Я никогда не ценил WebApi так сильно, как когда мне приходилось копаться в устаревшем коде WCF.

Другие вопросы по тегам