无法 return 应该是内部密封的模拟方法中的接口 class
Unable to return an interface in mocked method when it should be a internal sealed class
我们正在 XUnit 中为我们的 ASP.NET 应用程序项目之一创建单元测试。在项目中,我们试图模拟一个特定的第三方客户端,我们无法访问源代码并且不提供接口(我们可以创建的只是一个客户端对象)。为了解决这个问题,我们编写了一个包装器 class 和一个 class 的接口,这样我们就可以模拟出该客户端所需的功能。这部分都不错
我们在包装器 class 和接口中创建的方法之一是 GetInnerChannel 方法,用于从客户端(我们想要模拟)获取 属性。但是,该方法 return 是 System.ServiceModel.
的 IClientChannel 接口
public IClientChannel GetInnerChannel()
{
return client.InnerChannel;
}
这似乎无害,但是,在我们的模拟设置中,我们无法创建对我们正在测试的方法有用的假 IClientChannel 对象。这是我们的单元测试代码以供澄清:
client.Setup(i => i.GetInnerChannel()).Returns(GetClientChannel());
在 Returns 调用中,您会看到我们正在 returning 一个方法 return,我们目前暂时将其设置为 null。这是因为我们无法实例化接口。当我深入调试时,我发现在正常操作期间正在发送回接口的对象是一个 System.Runtime.Remoting.Proxies.__TransparentProxy 对象。对 __TransparentProxy class 的一点调查是它是一个内部密封的 class (这意味着我们不能在我们的单元测试代码中实例化它)。
不幸的是,我们正在测试的方法是这样使用 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 调用,我不会这样做,因此我们需要模拟它才能通过单元测试,因为我们必须模拟我们的客户端。
还有其他方法可以 return 对 GetInnerChannel() 有用的值或对象吗?我是否错过了模拟设置中的一个步骤?还是 Moq 和其他模拟框架无法完成我需要做的事情?还是我正在尝试针对无法进行单元测试的单元测试的方法?提前谢谢你。
基本上解决这个问题的方法是在 WCF 中使用大量包装器和接口。它很长,但是这个博客 post 做得更好。 https://weblogs.asp.net/cibrax/unit-tests-for-wcf
简而言之,如果你有静态的、密封的或其他第三方 class 你不能模拟,将它包装在一个简单的任务中,所有 public 方法都写在其中并调用它们您的第三方 class 的方法,然后为包装器创建一个接口。在你的单元测试中,使用接口,在你的正常代码中,使用你的包装器 class.
这无需再编写任何包装器即可实现。了解通过添加接口引入的间接原则很重要。添加的每个接口都是一个隔离抽象并使其外观和感觉不同的机会。
我附上了下面的重要片段,由于预期的简洁性,我没有附上整个解决方案。
classes 和用法层次结构的快速解释 -
1. IInnerChannel是第三方库对外暴露的接口。
2. IClientChannelWrapper 是包装器 class 创建的目的是为了隐藏调用客户端的内部接口。
3. ClassUsingChannelWrapper 是调用此逻辑的 class,在我们的单元测试中,它的方法将成为我们的 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;
}
}
调用包装器实现的 class。这个 class 将成为您实施单元测试时的 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);
}
}
最后,模拟两个接口行为的单元测试。请注意模拟的客户端通道包装器如何返回模拟的内部通道对象,该对象 returns 预编程值。
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();
}
}
运行 这个单元测试打印
"This is a test from mocked object."
本质上,您的单元测试仅针对尝试使用您的界面行为的客户端代码。这不会测试包装器的实现。如果你想实现这一点,你将必须创建一个新的包装器实例 class 而不是模拟对象,并将内部通道的模拟对象提供给它。
我们正在 XUnit 中为我们的 ASP.NET 应用程序项目之一创建单元测试。在项目中,我们试图模拟一个特定的第三方客户端,我们无法访问源代码并且不提供接口(我们可以创建的只是一个客户端对象)。为了解决这个问题,我们编写了一个包装器 class 和一个 class 的接口,这样我们就可以模拟出该客户端所需的功能。这部分都不错
我们在包装器 class 和接口中创建的方法之一是 GetInnerChannel 方法,用于从客户端(我们想要模拟)获取 属性。但是,该方法 return 是 System.ServiceModel.
的 IClientChannel 接口public IClientChannel GetInnerChannel()
{
return client.InnerChannel;
}
这似乎无害,但是,在我们的模拟设置中,我们无法创建对我们正在测试的方法有用的假 IClientChannel 对象。这是我们的单元测试代码以供澄清:
client.Setup(i => i.GetInnerChannel()).Returns(GetClientChannel());
在 Returns 调用中,您会看到我们正在 returning 一个方法 return,我们目前暂时将其设置为 null。这是因为我们无法实例化接口。当我深入调试时,我发现在正常操作期间正在发送回接口的对象是一个 System.Runtime.Remoting.Proxies.__TransparentProxy 对象。对 __TransparentProxy class 的一点调查是它是一个内部密封的 class (这意味着我们不能在我们的单元测试代码中实例化它)。
不幸的是,我们正在测试的方法是这样使用 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 调用,我不会这样做,因此我们需要模拟它才能通过单元测试,因为我们必须模拟我们的客户端。
还有其他方法可以 return 对 GetInnerChannel() 有用的值或对象吗?我是否错过了模拟设置中的一个步骤?还是 Moq 和其他模拟框架无法完成我需要做的事情?还是我正在尝试针对无法进行单元测试的单元测试的方法?提前谢谢你。
基本上解决这个问题的方法是在 WCF 中使用大量包装器和接口。它很长,但是这个博客 post 做得更好。 https://weblogs.asp.net/cibrax/unit-tests-for-wcf
简而言之,如果你有静态的、密封的或其他第三方 class 你不能模拟,将它包装在一个简单的任务中,所有 public 方法都写在其中并调用它们您的第三方 class 的方法,然后为包装器创建一个接口。在你的单元测试中,使用接口,在你的正常代码中,使用你的包装器 class.
这无需再编写任何包装器即可实现。了解通过添加接口引入的间接原则很重要。添加的每个接口都是一个隔离抽象并使其外观和感觉不同的机会。
我附上了下面的重要片段,由于预期的简洁性,我没有附上整个解决方案。
classes 和用法层次结构的快速解释 - 1. IInnerChannel是第三方库对外暴露的接口。 2. IClientChannelWrapper 是包装器 class 创建的目的是为了隐藏调用客户端的内部接口。 3. ClassUsingChannelWrapper 是调用此逻辑的 class,在我们的单元测试中,它的方法将成为我们的 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;
}
}
调用包装器实现的 class。这个 class 将成为您实施单元测试时的 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);
}
}
最后,模拟两个接口行为的单元测试。请注意模拟的客户端通道包装器如何返回模拟的内部通道对象,该对象 returns 预编程值。
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();
}
}
运行 这个单元测试打印
"This is a test from mocked object."
本质上,您的单元测试仅针对尝试使用您的界面行为的客户端代码。这不会测试包装器的实现。如果你想实现这一点,你将必须创建一个新的包装器实例 class 而不是模拟对象,并将内部通道的模拟对象提供给它。