如何使用Moq模拟WCF客户端?

Dha*_*esh 4 wcf unit-testing asynchronous moq system.reactive

在我的项目中,我正在使用:SL5 + MVVM + Prism + WCF + Rx + Moq + Silverlight单元测试框架.

我是单元测试的新手,最近开始使用DI,模式(MVVM)等.因此,下面的代码有很大的改进空间(如果您这么认为,请随意拒绝我正在采取的整个方法).

为了访问我的WCF服务,我创建了一个如下所示的工厂类(再次,它可能有缺陷,但请看一下):

namespace SomeSolution
{
    public class ServiceClientFactory:IServiceClientFactory
    {
        public CourseServiceClient GetCourseServiceClient()
        {
            var client = new CourseServiceClient();
            client.ChannelFactory.Faulted += (s, e) => client.Abort();
            if(client.State== CommunicationState.Closed){client.InnerChannel.Open();}
            return client;
        }

        public ConfigServiceClient GetConfigServiceClient()
        {
            var client = new ConfigServiceClient();
            client.ChannelFactory.Faulted += (s, e) => client.Abort();
            if (client.State == CommunicationState.Closed) { client.InnerChannel.Open(); }
            return client;
        }

        public ContactServiceClient GetContactServiceClient()
        {
            var client = new ContactServiceClient();
            client.ChannelFactory.Faulted += (s, e) => client.Abort();
            if (client.State == CommunicationState.Closed) { client.InnerChannel.Open(); }
            return client;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它实现了一个简单的界面如下:

public interface IServiceClientFactory
{
    CourseServiceClient GetCourseServiceClient();
    ConfigServiceClient GetConfigServiceClient();
    ContactServiceClient GetContactServiceClient();
}
Run Code Online (Sandbox Code Playgroud)

在我的VM中,我正在进行上述类的DI并使用Rx调用WCF,如下所示:

var client = _serviceClientFactory.GetContactServiceClient();
try
{

    IObservable<IEvent<GetContactByIdCompletedEventArgs>> observable =
        Observable.FromEvent<GetContactByIdCompletedEventArgs>(client, "GetContactByIdCompleted").Take(1);

    observable.Subscribe(
        e =>
            {
                if (e.EventArgs.Error == null)
                {                                    
                    //some code here that needs to be unit-tested

                }
            },
        ex =>
        {
            _errorLogger.ProcessError(GetType().Name, MethodBase.GetCurrentMethod().Name, ErrorSource.Observable, "", -1, ex);
        }
        );
    client.GetContactByIdAsync(contactid, UserInformation.SecurityToken);
}
catch (Exception ex)
{
    _errorLogger.ProcessError(GetType().Name, MethodBase.GetCurrentMethod().Name, ErrorSource.Code, "", -1, ex);
}
Run Code Online (Sandbox Code Playgroud)

现在我想构建单元测试(是的,它不是TDD).但我不明白从哪里开始.使用Moq,我无法模拟BlahServiceClient.此外,没有svcutil生成的接口可以提供帮助,因为异步方法不是自动生成的IBlahService接口的一部分.我可能更喜欢扩展(通过部分类等)任何自动生成的类,但我不愿意选择手动构建svcutil可以生成的所有代码(坦率地考虑时间和预算).

有人可以帮忙吗?任何正确方向的指针都会对我有所帮助.

Qui*_*rdt 10

模拟服务客户端时,实际上是在模拟它实现的接口之一.所以在你的情况下它可能是IContactService.

生成的代码实现了System.ServiceModel.ClientBase<IContactService>IContactService.您的依赖提供程序(在您的情况下是工厂)正在返回ContactServiceClient- 将此更改IContactService为初学者.这将有助于您现在和未来的DI.

好的,你已经有了一个抽象工厂,现在他们返回你的服务界面IContactService.你现在只使用接口,所以模拟是非常微不足道的.

首先是一些关于你想要运用的代码的假设.代码段提供了抽象工厂和服务客户​​端的消息.如果//这里需要进行单元测试的 部分代码不会与任何其他依赖项进行交互,那么您需要模拟出工厂和服务客户​​端,以便您将测试与该方法隔离开来身体代码.

为了举个例子,我做了一个调整.你的界面:

public class Contact {
    public string Name { get; set; }
}

public interface IContactService {
    Contact GetContactById(int contactid);
}

public interface IContactServiceFactory {
    IContactService GetContactService();
}
Run Code Online (Sandbox Code Playgroud)

然后你的测试看起来总结如下:

public void WhateverIsToBeTested_ActuallyDoesWhatItMustDo() {

    // Arrange
    var mockContactService = new Mock<IContactService>();
    mockContactService.Setup(cs => cs.GetContactById(It.IsAny<int>()))
        .Returns(new Contact { Name = "Frank Borland" });

    var fakeFactory = new Mock<IContactServiceFactory>();
    fakeFactory.Setup(f => f.GetContactService())
        .Returns(mockContactService.Object);

    /* i'm assuming here that you're injecting the dependency intoto your factory via the contructor - but 
     * assumptions had to be made as not all the code was provided
     */
    var yourObjectUnderTest = new MysteryClass(fakeFactory.Object);

    // Act
    yourObjectUnderTest.yourMysteryMethod();

    // Assert
    /* something mysterious, but expected, happened */            

}
Run Code Online (Sandbox Code Playgroud)

编辑:模拟异步方法

异步生成的方法不是服务方法的一部分,而是由WCF作为Client类的一部分创建的.要将这些模拟为接口,请执行以下操作:

  1. 提取ContactServiceClient类的接口.在VS中,它只是右键单击(在类名称上),重构,提取界面.并只选择适用的方法.

  2. ContactServiceClient班是部分如此创建一个新的类文件,并重新定义ContactServiceClient类来实现新的IContactServiceClient,你刚才提取接口.

    public partial class ContactServiceClient : IContactServiceClient {
    }
    
    Run Code Online (Sandbox Code Playgroud)

像这样,现在客户端类ALSO使用选定的异步方法实现新接口.刷新服务接口并重新生成服务类时 - 您不必重新提取接口,因为您已使用接口引用创建了单独的部分类.

  1. 创建一个新工厂以返回新接口

    public interface IContactServiceClientFactory {
        IContactServiceClient GetContactServiceClient();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 修改测试以使用该接口.