当它应该是内部密封类时,无法在模拟方法中返回接口

问题描述 投票:0回答:2

我们将在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和其他模拟框架无法执行我需要做的事情?还是我尝试针对无法进行单元测试的单元测试的方法?预先谢谢你。

c# unit-testing moq xunit.net
2个回答
0
投票

基本上,解决方案是在WCF中使用许多包装程序和接口。这很长,但是这篇博客文章做得更好。 https://weblogs.asp.net/cibrax/unit-tests-for-wcf

简而言之,如果您有静态,密封或其他第三方类,则无法模拟,将其包装在一个简单的任务中,并在其中编写所有公共方法,并使用它们调用您的第三方类的方法,然后为包装器。在单元测试中,使用接口,在常规代码中,使用包装器类。


0
投票

这是可能的,而无需编写任何其他包装。了解添加接口引入的间接原理很重要。添加的每个接口都是隔离抽象并使其外观不同的机会。

由于期望的简短性,我在下面附上了重要的摘要,因此我没有附上完整的解决方案。

类别和用法层次的快速说明-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();

    }
}

运行此单元测试打印

“这是来自模拟对象的测试。”

本质上,您的单元测试仅针对尝试使用界面行为的客户端代码。这不会测试包装程序的实现。如果要实现这一点,则必须创建一个包装类的新实例而不是模拟对象,并将其提供给内部通道的模拟对象。

© www.soinside.com 2019 - 2024. All rights reserved.