【问题标题】:How to mock WCF client using Moq?如何使用 Moq 模拟 WCF 客户端?
【发布时间】:2012-12-27 23:10:34
【问题描述】:

在我的项目中,我使用的是: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;
        }
    }
}

它实现了一个简单的接口如下:

public interface IServiceClientFactory
{
    CourseServiceClient GetCourseServiceClient();
    ConfigServiceClient GetConfigServiceClient();
    ContactServiceClient GetContactServiceClient();
}

在我的虚拟机中,我正在执行上述类的 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);
}

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

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

【问题讨论】:

  • "//这里有些代码需要进行单元测试" - 什么样的代码?因为我认为你在测试错误的东西。如果您想对代码进行单元测试,请在没有 WCF 和所有服务基础架构的隔离场景中对其进行测试。
  • 这是非常真实的迈克尔。我应该重构我的代码以将其他逻辑与 WCF 调用隔离开来。非常感谢。
  • @user697942 这里没有太多需要重构的地方。只需将您的“一些代码”放入它自己的方法中并测试该方法。那么就不需要测试 WCF 的东西了。
  • @Michal,就我而言,我刚刚完成了创建服务。我们希望单元测试来确保通过 wsdl 进行调用总是有效的,所以我们需要一些测试。所以我正在尝试对我的服务方法进行单元测试……就好像我是一个使用 Web 服务的客户一样。所以我的意图是调用 WSDL 中指定的方法作为我的 Web 服务的假设客户端/消费者。现在另一方面,我可能还想在我的服务项目本身中创建单元测试来测试方法?不确定要单元测试什么。
  • client.ChannelFactory 在做什么?

标签: wcf unit-testing asynchronous moq system.reactive


【解决方案1】:

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

生成的代码同时实现了System.ServiceModel.ClientBase&lt;IContactService&gt;IContactService。您的依赖提供者(在您的情况下是工厂)正在返回 ContactServiceClient - 对于初学者,将其更改为 IContactService。这将有助于您现在和将来的 DI。

好的,你已经有了一个抽象工厂,现在它们返回你的服务接口IContactService。您现在才使用接口,所以模拟非常简单。

首先对您尝试练习的代码进行一些假设。代码 sn-p 提供了抽象工厂和服务客户​​端的消息。假设 //此处需要进行单元测试的部分代码 部分不会与任何其他 依赖项 交互,那么您希望模拟出这两个工厂和服务客户​​端,以便您测试仅与方法主体代码隔离。

为了举例,我做了一些调整。你的界面:

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

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

public interface IContactServiceFactory {
    IContactService GetContactService();
}

那么你的测试总结如下:

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 */            

}

编辑:模拟异步方法

异步生成的方法不是您的服务方法的一部分,而是由 WCF 作为客户端类的一部分创建的。要将它们模拟为接口,请执行以下操作:

  1. 提取ContactServiceClient 类的接口。在 VS 中,只需右键单击(在类名上)、重构、提取接口。并且只选择适用的方法。

  2. ContactServiceClient 类是部分的,因此创建一个新类文件并重新定义 ContactServiceClient 类以实现您刚刚提取的新IContactServiceClient 接口。

    public partial class ContactServiceClient : IContactServiceClient {
    }
    

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

  1. 新建工厂返回新界面

    public interface IContactServiceClientFactory {
        IContactServiceClient GetContactServiceClient();
    }
    
  2. 修改测试以使用该接口。

【讨论】:

  • 谢谢,但 IContactService 没有在其中定义开箱即用的 GetContactByIdAsync(或任何异步方法),这是主要问题。我不知道微软为什么这样做。
  • 您不必手动编写异步接口代码即可获得相同的结果。我会把它添加到我的答案中
  • @QuintonBernhardt:您对 Async 问题的解决方案是有问题的,因为代码生成的整个想法是“正常工作”。如果 OP 切换到使用手动提取的 ICourseServiceClient,那么下次 IDL 更改时,他将不得不重新提取接口以使用其代码中的更改。可行 - 但烦人且容易出错。
  • 有用的答案。但是,与 OP 的问题有关的“联系”和“课程”类在您的代码中搞砸了。您介意只使用其中一个吗?
  • @Marcel:很好看!谢谢。我已经更正了对课程的引用。当在代码示例中明确使用 ContactService 接口时,OP 省略了定义 - 我现在已经添加了。
猜你喜欢
  • 2015-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-18
  • 1970-01-01
  • 2010-11-07
  • 2011-05-21
  • 1970-01-01
相关资源
最近更新 更多