在我的项目中,我使用: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。此外,没有自动生成的接口可以提供帮助,因为异步方法不是自动生成的IBlahService接口的一部分。我可能更喜欢扩展(通过部分类等)任何自动生成的类,但我不愿意选择手动构建svcutil可以生成的所有代码(坦率地说,考虑到时间和预算)。
有人能帮帮我吗?任何指向正确方向的指针都会对我有很大帮助。
在模拟服务客户端时,实际上是在模拟它实现的一个接口。在你的例子中,它可能是IContactService
。
生成的代码实现了System.ServiceModel.ClientBase<IContactService>
和IContactService
。您的依赖提供程序(在您的例子中是工厂)返回ContactServiceClient
—对于初学者,将其更改为IContactService
。这将在现在和将来帮助您的DI。
好的,你已经有了一个抽象工厂,现在他们返回你的服务接口IContactService
。您现在只使用接口,所以mock是相当微不足道的。
首先对你要执行的代码做一些假设。代码片段同时为抽象工厂和服务客户端提供消息。假设这里需要进行单元测试的部分的一些代码不打算与任何其他依赖项交互,那么您希望模拟出工厂和服务客户端,以便您的测试仅与方法体代码隔离。
为了便于示例,我做了一些调整。您的接口:
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 */
}
EDIT: Mocking the Async Methods
异步生成的方法不是服务方法的一部分,而是由WCF作为Client类的一部分创建的。要将其模拟为接口,请执行以下操作:提取
ContactServiceClient
类的接口。在VS中,只需右键单击(在类名上),重构,提取接口。ContactServiceClient
类是局部的,所以创建一个新类文件并重新定义ContactServiceClient类来实现您刚刚提取的新的IContactServiceClient
接口。public partial class ContactServiceClient : IContactServiceClient { }
像这样,现在客户端类也实现了新的接口与选择的异步方法。当刷新服务接口并重新生成服务类时,您不必重新提取接口,因为您已经使用接口引用创建了一个单独的部分类。
创建新工厂返回新接口
public interface IContactServiceClientFactory { IContactServiceClient GetContactServiceClient(); }
对于。net core,我编写了以下代码片段:
// Set up the mock client for the RelatieSoapClient.
var relatieSoapClientMock = new Mock<RelatieSoapClient>(
new WSHttpBinding(),
new EndpointAddress("http://localhost:8000"));
// Set up the mock response for the Get method.
var relatieSoapMock = new Mock<RelatieSoap>();
relatieSoapMock.Setup(x => x.Get(It.IsAny<GetRequest>())).Returns(new GetResponse
{
GetResult = resultContainer
});
// Set up the protected CreateChannel method to return the mock client.
var relatieSoapClientProtectedMock = relatieSoapClientMock.Protected()
.Setup<RelatieSoap>("CreateChannel")
.Returns(relatieSoapMock.Object);