我们将在XUnit中为我们的ASP.NET应用程序项目创建单元测试。在该项目中,我们试图模拟一个特定的第三方客户端,我们无法访问源代码,并且不提供接口(只是我们可以创建的客户端对象)。为了解决这个问题,我们编写了包装器类和该类的接口,以便我们可以模拟出该客户端所需的功能。这部分都很好。
我们在包装器类和接口中创建的方法之一是GetInnerChannel方法,该方法用于从客户端获取属性(我们要模拟出该属性)。但是,该方法从System.ServiceModel返回IClientChannel的接口。
public IClientChannel GetInnerChannel()
{
return client.InnerChannel;
}
似乎无害,但是在我们的模拟设置中,我们无法创建对我们正在测试的方法有用的伪造的IClientChannel对象。这是我们的单元测试代码,以供澄清:
client.Setup(i => i.GetInnerChannel()).Returns(GetClientChannel());
在Returns调用中,您将看到我们正在返回一个方法return,目前我们将其设置为null。这是因为我们无法实例化接口。当我深入调试时,我发现在正常操作期间要代替接口发送回的对象是System.Runtime.Remoting.Proxies .__ TransparentProxy对象。对__TransparentProxy类进行的一些调查是它是一个内部密封的类(这意味着我们无法在单元测试代码中实例化该类)。
不幸的是,我们正在测试的方法是以这种方式使用InnerChannel:
public List<EquifaxQuestion> GetEquifaxQuestions(User basicUserInfo, IEnumerable<AddressViewModel> Addresses)
{
try
{
InitialResponse response;
using (new OperationContextScope(client.GetInnerChannel()))
{
OperationContext.Current.OutgoingMessageHeaders.Add(
new EquifaxSecurityHeader(appID, username, password));
response = client.SubmitInitialInteraction(GenerateInitialRequest(basicUserInfo, Addresses));
}
我不知道是否可以替换GetInnerChannel调用,因此我们需要模拟它才能通过单元测试,因为我们必须模拟出客户端。
还有另一种方法可以返回对GetInnerChannel()有用的值或对象吗?我是否在模拟设置中缺少步骤?还是Moq和其他模拟框架无法执行我需要做的事情?还是我尝试针对无法进行单元测试的单元测试的方法?预先谢谢你。
基本上,解决方案是在WCF中使用许多包装程序和接口。这很长,但是这篇博客文章做得更好。 https://weblogs.asp.net/cibrax/unit-tests-for-wcf
简而言之,如果您有静态,密封或其他第三方类,则无法模拟,将其包装在一个简单的任务中,并在其中编写所有公共方法,并使用它们调用您的第三方类的方法,然后为包装器。在单元测试中,使用接口,在常规代码中,使用包装器类。
这是可能的,而无需编写任何其他包装。了解添加接口引入的间接原理很重要。添加的每个接口都是隔离抽象并使其外观不同的机会。
由于期望的简短性,我在下面附上了重要的摘要,因此我没有附上完整的解决方案。
类别和用法层次的快速说明-1. IInnerChannel是第三方库公开的接口。2. IClientChannelWrapper是创建的包装器类,用于对调用客户端隐藏内部接口。3. ClassUsingChannelWrapper是调用此逻辑的类,在我们的单元测试中,其方法将是我们的sut(正在测试的对象)。
代码如下-
IInnerChannel接口声明-
public interface IInnerChannel
{
string TheInnerChannelMethod();
}
InnerChannel实现(可能在您的情况下在第三方库中)-
public class InnerChannelImplementation : IInnerChannel
{
public InnerChannelImplementation()
{
}
public string TheInnerChannelMethod()
{
var result = "This is coming from innser channel.";
Console.WriteLine(result);
return result;
}
}
由您围绕内部通道创建的包装-
public interface IClientChannelWrapper
{
void DoSomething();
IInnerChannel GetTheInnerChannelMethod();
}
包装器接口的实现-
public class ClientChannelWrapperImplementation : IClientChannelWrapper
{
public ClientChannelWrapperImplementation()
{
}
public void DoSomething()
{
Console.WriteLine("The DoSomething Method!");
}
public IInnerChannel GetTheInnerChannelMethod()
{
InnerChannelImplementation imp = new InnerChannelImplementation();
return imp;
}
}
调用包装器实现的类。实施单元测试时,该类将成为您的SUT-
public class ClassUsingChannelWrapper
{
IClientChannelWrapper _wrapper;
public ClassUsingChannelWrapper(IClientChannelWrapper wrapper)
{
_wrapper = wrapper;
}
public void TheClientChannelConsumerMethod()
{
IInnerChannel theChannel = _wrapper.GetTheInnerChannelMethod();
var result = theChannel.TheInnerChannelMethod();
Console.WriteLine(result);
}
}
最后,带有模拟两个接口行为的模拟的单元测试。请注意,模拟的客户端通道包装程序如何返回模拟的内部通道对象,该对象返回预编程的值。
public class UnitTest1
{
[Fact]
public void Test1()
{
//Arrange
Mock<IInnerChannel> innerChannelMock = new Mock<IInnerChannel>();
innerChannelMock.Setup(i => i.TheInnerChannelMethod()).Returns("This
is a test from mocked object.");
Mock<InterfaceUt.IClientChannelWrapper> mockClientWrapper = new
Mock<IClientChannelWrapper>();
mockClientWrapper.Setup(m =>
m.GetTheInnerChannelMethod()).Returns(innerChannelMock.Object);
//Act
ClassUsingChannelWrapper sut = new
ClassUsingChannelWrapper(mockClientWrapper.Object);
sut.TheClientChannelConsumerMethod();
//Assert
innerChannelMock.Verify();
mockClientWrapper.Verify();
}
}
运行此单元测试打印
“这是来自模拟对象的测试。”
本质上,您的单元测试仅针对尝试使用界面行为的客户端代码。这不会测试包装程序的实现。如果要实现这一点,则必须创建一个包装类的新实例而不是模拟对象,并将其提供给内部通道的模拟对象。