Невозможно вернуть интерфейс в смоделированном методе, когда он должен быть внутренним запечатанным классом - PullRequest
0 голосов
/ 24 октября 2019

Мы создаем модульные тесты в XUnit для одного из наших проектов - нашего приложения ASP.NET. В проекте мы пытаемся смоделировать конкретного стороннего клиента, у которого нет доступа к исходному коду и не предоставляется интерфейс (просто объект клиента, который мы можем создать). Чтобы исправить это, мы написали класс-оболочку и интерфейс для этого класса, чтобы мы могли смоделировать функциональность, которая нам нужна для этого клиента. Эта часть хороша.

Одним из методов, которые мы создали в классе-оболочке и интерфейсе, был метод GetInnerChannel для получения свойства от клиента (который мы хотим смоделировать). Однако этот метод возвращает интерфейс IClientChannel из System.ServiceModel.

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 и другие фальшивые фреймворки не способны делать то, что мне нужно? Или метод, который я пытаюсь выполнить модульным тестом, не может быть протестирован? Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 26 октября 2019

Это возможно без необходимости писать больше обёрток. Важно понимать принцип косвенности, который вводится при добавлении интерфейса. Каждый добавленный интерфейс - это возможность изолировать абстракцию и заставить ее выглядеть и чувствовать себя по-разному.

Я добавил важные фрагменты ниже, из-за ожидаемой краткости, я не присоединяю все решение.

Быстрыйобъяснение классов и иерархии использования - 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();

    }
}

При выполнении этого модульного теста печатается

"Это тест из смоделированного объекта."

По сути, ваш модульный тест ориентирован только на клиентский код, который пытается использовать поведение вашего интерфейса. Это не проверяет реализацию вашей оболочки. Если вы хотите добиться этого, вам нужно будет создать новый экземпляр класса-оболочки вместо фиктивного объекта и передать ему фиктивный объект внутреннего канала.

0 голосов
/ 25 октября 2019

В основном решением для этого было множество оболочек и интерфейсов для работы в WCF. Это довольно долго, но этот блог делает лучше. https://weblogs.asp.net/cibrax/unit-tests-for-wcf

Короче говоря, если у вас есть статический, запечатанный или другой сторонний класс, который вы не можете смоделировать, оберните его в простую задачу со всеми открытыми методами, написанными в них, с вызовом вашего стороннего класса. метод, а затем создать интерфейс для оболочки. В своих модульных тестах используйте интерфейс, а в обычном коде - класс-оболочку.

...