Макет метода з out параметром за допомогою Moq

6 хв. читання

Код проекту

Вступ

Moq — зручна та популярна бібліотека створення макетів об'єктів для платформи .Net. Вона використовується при модульному тестуванні, щоб ізолювати окремі класи від їх залежностей та впевнитись, що виконуються лише очікувані методи залежних об'єктів.

Коли створюються макети об'єктів, їх методи, які мають бути виконані, встановлюються за допомогою різноманітних перевизначень методу Setup. Проте коректно побудувати макет об'єкта може бути досить складною задачею, наприклад, коли метод має ref/out параметри чи є статичним. Нижче розглянуто випадок побудови макета метода з out параметром.

В статті використовуються функціональності, які описано в документі Moq's Quickstart.

Перелік технологій

Наведена програма використовує C#7, .Net 4.6.1, та NuGet пакети Moq, FluentAssertions та xUnit.

Постановка задачі

Розглянемо інтерфейс сервісу, який виставляє метод з out параметром:

// IService.cs
public interface IService
{
    void ProcessValue(string inputValue, out string outputValue);
}

та простий клас, що використовує цей метод:

// Class1.cs
public class Class1
{
    private readonly IService _service;
 
    public Class1(IService service)
    {
        _service = service;
    }
 
    /// <summary>
    /// Повертає усічене значення вхідного рядка, обробленого сервісом.
    /// </summary>
    /// <param name="inputValue">Вхідне значення, може бути null.</param>
    /// <param name="outputValue">Вихідне значення.</param>
    public string ProcessValue(string inputValue)
    {
        _service.ProcessValue(inputValue, out string outputValue);
        return outputValue;
    }
}

Потрібно написати модульні тести для методу ProcessValue класу Class1. Очевидним чином для цього необхідно створити макет об'єкту сервісу Service1.

Макет сервісу без методу зворотного виклику

Як зазначено в документації Moq's Quickstart, макет методу з out параметром може бути задано наступним кодом:

// вихідні аргументи
var outString = "ack";
// TryParse поверне true, а вихідний аргумент буде мати значення "ack", що присвоюєно відкладеним чином
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);

Цей підхід використаємо в першому тесті:

[Theory]
[InlineData("input")]
public void Class1_Return_ConstValueWithoutCallback(string inputValue)
{
    var expectedValue = "Expected value";
    var service = new Mock<Service1>();
    service
        .Setup(mock => mock.ProcessValue(It<string>.IsAny(), out expectedValue))
        .Verifiable();
 
    var class1 = new Class1(service.Object);
    var actualValue = class1.ProcessValue(inputValue);
 
    actualValue.Should().NotBeNull();
    actualValue.Should().Be(expectedValue);
 
    service.Verify();
}

В цьому випадку out параметр має попередньо задане значення, яке не залежить від решти змінних, що використовує тест. Ба більше, якщо твердження тесту припускає, що значення out параметру залежить від вхідних параметрів тесту, то попередній тест можна змінити таким чином:

[Theory]
[InlineData("input")]
public void Class1_Return_TheSameValueWithoutCallback(string inputValue)
{
    var expectedValue = $"Output {inputValue}";
    var service = new Mock<Service1>();
    service
        .Setup(mock => mock.ProcessValue(It<string>.IsAny(), out expectedValue))
        .Verifiable();
    // той же код
    // ...
}

Даний підхід може бути корисним, проте, якщо необхідно отримати значення решти параметрів, які передано до методу сервісу на кшталт inputValue, чи встановити певне значення out параметру, то потрібно при створенні макету використовувати метод Callback.

Макет сервісу із методом зворотного виклику

Відповідно до Moq's Quickstart, можна побудувати макет методу із ref / out параметрами з методом зворотного виклику:

// методи зворотного виклику можливі для методів з `ref` / `out` параметрами, але вони вимагають додаткової роботи (та Moq 4.8+):
delegate void SubmitCallback(ref Bar bar);
 
mock.Setup(foo => foo.Submit(ref It<Bar>.Ref.IsAny))
    .Callback(new SubmitCallback((ref Bar bar) => Console.WriteLine("Submitting a Bar!")));

Тому потрібно визначити делегат з точно таким же переліком параметрів, що і метод ProcessValue із сервісу IService:

private delegate void ServiceProcessValue(string inputValue, out string outputValue);

Тепер макет сервісу IService можна визначити за допомогою методу Callback:

[Theory]
[InlineData("input")]
public void Class1_Return_NewValueWithCallback(string inputValue)
{
    string actualInputValue = null;
 
    const string outputValue = "Inner value";
    var expectedValue = "Not used value";
    var service = new Mock<Service1>();
    service
        .Setup(mock => mock.ProcessValue(It<string>.IsAny(), out expectedValue))
        .Callback(new ServiceProcessValue(
            (string input, out string output) =>
            {
                actualInputValue = input;
                output = outputValue;
            }))
        .Verifiable();
 
    var class1 = new Class1(service.Object);
    var actualValue = class1.ProcessValue(inputValue);
 
    actualValue.Should().NotBeNull();
    actualValue.Should().Be(outputValue);
 
    actualInputValue.Should().Be(inputValue);
 
    service.Verify();
}

Метод Callback використовує делегат для того, щоб зберегти значення outputValue в out параметрі, як показано в 16-тому рядку, та запам'ятати значення переданих параметрів, як показано в 15-тому рядку. Зазначимо, що при цьому значення, яке було використано в визначенні метода Setup, не використовується. Цей факт безпосередньо перевіряється на 26-тому рядку.

Код проекту

Твердження статті демонструються в проекті, що складається із консольного застосунку ConsoleApp та бібліотеки тестів ConsoleApp.Tests.

Консольний застосунок ConsoleApp містить інтерфейс IService та клас Service, як реалізацію інтерфейсу, та клас Class1, що використовує сервіс. В головному методі застосунку створюється об'єкт класу та метод ProcessValue перевіряється на декількох значеннях.

Бібліотека тестів ConsoleApp.Tests містить тести, що розглянуто вище.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація