Почему я не могу изменить значение свойства фиктивного объекта, которое не было настроено?

Я новичок в модульном тестировании и пишу модульные тесты для приложений .NET C#. Для создания макетов я использую пакет MOQ NuGet в своем тестовом проекте. У меня есть интерфейс с двумя свойствами:

public interface IUserInput
{
    public int FirstInput { get; set; }
    public int SecondInput { get; set; }
}

У меня есть класс, который работает с этим интерфейсом:

public class CalculatorCore : ICalculatorBase
{
    private readonly IUserInput _input;

    public CalculatorCore(IUserInput input)
    {
        _input = input;
    }

    public int AddByUserInput()
    {
        _input.SecondInput += 2;
        return _input.FirstInput + _input.SecondInput;
    }
}

Для этого класса я написал тестовый класс и тестовый метод:

[TestClass]
public class CalculatorCoreTest
{
    [TestMethod]
    public void AddByUserInput()
    {
        Mock<IUserInput> userInputMock = new Mock<IUserInput>();
        userInputMock.SetupProperty(f => f.FirstInput, 1);
        userInputMock.SetupProperty(f => f.SecondInput, 1);

        var calculatorCore = new CalculatorCore(userInputMock.Object);

        var actual = calculatorCore.AddByUserInput();
        Assert.AreEqual(actual, 4);
    }
}

Этот тест пройден, и у меня нет никаких проблем.
Однако, когда я удаляю строку

  userInputMock.SetupProperty(f => f.SecondInput, 1);

и измените часть кода утверждения на

Assert.AreEqual(actual, 3);

тест не пройден, и мой результат равен 1. Это происходит потому, что когда я добавляю 2 к свойству фиктивного объекта, оно все равно равно 0;

Почему я не могу изменить значение свойства фиктивного объекта, которое не было настроено?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Без настройки свойство невозможно установить. Чтобы сделать это визуально, оно будет действовать как следующее свойство:

int SecondInput { set {} get => default; }

Поэтому он всегда будет возвращать 0. Если вы хотите, чтобы все имитируемые свойства действовали как реальные свойства, вы также можете вызвать SetupAllProperties() на своем макете, чтобы быстро настроить желаемое поведение.

Благодарю за ваш ответ. Ваш ответ прояснил мою проблему и способы ее решения.

Harutyun Dokhoyan 11.04.2024 16:34

Этот вопрос, а также ответ от NotFound предполагают, что тестируемый API может быть улучшен.

Вместо того, чтобы использовать Moq для предоставления экземпляра IUserInput, почему бы вам не создать правильный класс?

public sealed class UserInput : IUserInput
{
    public int FirstInput { get; set; }
    public int SecondInput { get; set; }
}

Это делает тест более простым и надежным:

[Fact]
public void AddByUserInput()
{
    var userInput = new UserInput { FirstInput = 1, SecondInput = 1 };
    var calculatorCore = new CalculatorCore(userInput);

    var actual = calculatorCore.AddByUserInput();

    Assert.Equal(4, actual);
}

В самом деле, если вы это сделали, то зачем вам интерфейс?

Какова цель интерфейса с двумя свойствами? Интерфейс — это способ внедрить полиморфизм в код C#, но сколько существует допустимых способов реализовать свойство чтения/записи? Вам действительно нужно изменить это поведение?

Как следует из ответа NotFound, существует несколько неправильных способов реализации интерфейса типа IUserInput, но в большинстве случаев только один полезный способ.

Таким образом, если у вас нет веских причин сохранить интерфейс, измените CalculatorCore, чтобы в качестве входных данных использовался конкретный класс:

public class CalculatorCore : ICalculatorBase
{
    private readonly UserInput _input;

    public CalculatorCore(UserInput input)
    {
        _input = input;
    }

    public int AddByUserInput()
    {
        _input.SecondInput += 2;
        return _input.FirstInput + _input.SecondInput;
    }
}

или, возможно, решить проблему Feature Envy , применив рефакторинг Move Method:

public sealed class UserInput
{
    public int FirstInput { get; set; }
    public int SecondInput { get; set; }

    public int AddByUserInput()
    {
        SecondInput += 2;
        return FirstInput + SecondInput;
    }
}

Это еще больше упрощает тест:

[Fact]
public void AddOnUserInput()
{
    var userInput = new UserInput { FirstInput = 1, SecondInput = 1 };
    var actual = userInput.AddByUserInput();
    Assert.Equal(4, actual);
}

Это хороший пример того, как написание тестов проливает полезный свет на проектирование тестируемой системы.

Благодарю за ваш ответ. Я полностью согласен с вашими соображениями. Кроме того, я ценю, что вы нашли время ответить мне. Я просто хотел привести пример, иллюстрирующий проблему, возникающую при изучении модульного тестирования. При написании реального кода я буду придерживаться всех необходимых принципов.

Harutyun Dokhoyan 11.04.2024 16:39

Другие вопросы по теме