Подтвердить что ты не робот

Как смоделировать метод с выходным параметром?

Я использую библиотеку, которая использует параметры в функции, и мне нужно проверить мой код с помощью этой функции.

Итак, попытка сделать издевательства пришла мне на помощь, через Moq, который я использовал в остальной части проекта.

Вопрос

Я знаю там стену ниже, поэтому вопрос (заранее):

  • На приведенном ниже рисунке: Может ли Moq издеваться над элементом с конструктором, который требует параметров, которые обычно не вызываются сами по себе?
  • Это проблема с моим тестовым кодом? С библиотекой? С помощью библиотеки проверки?
  • Я использую Moq без параметров?
  • Где я даже начинаю отлаживать это?

Обновление: ведет до сих пор

Я думаю, что это проблема на насмешливой стороне, насмехаясь над интерфейсом IXLRow. Обычно кажется, что XLRow только создается из книги и никогда через new XLRow() - это фактор?

Следующий тест проходит, когда (примечание: mocks):

   [Fact]
    public void TryGetValueCanReturnTrueForVieldWithAnInteger_WhenAccessingFromRow()
    {
        var workbook = new XLWorkbook();
        workbook.Worksheets.Add("TestWS");
        var wb = workbook.Worksheet("TestWS");
        wb.Cell("A1").Value = "12345";

        // NOTE: Here we're referring to the row as part of an instantiated  
        //       workbook instead of Mocking it by itself
        int output;
        Assert.True(wb.Row(1).Cell("A").TryGetValue(out output));
    }

Код

Фрагмент метода, который получает макет действительного объекта():

// ...other code that sets up other parts of the row correctly
int isAnyInt = 0; //I don't care about this value, only the true/false

// set this to false to true to mimic a row being a legitimate integer
mock.Setup(m => m.Cell("B").TryGetValue(out isAnyInt)).Returns(true);

xUnit test, который проверяет счастливый путь. Получает макет допустимой строки, а затем обеспечивает ее проверку. ПРИМЕЧАНИЕ. Этот тест проходит.

    [Fact]
    public void Validate_GivenValidRow_ReturnsValid()
    {
        var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();

        var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);
        Assert.True(validationResult.IsValid);
    }

тест xUnit (в основном, "не работает ли валидатор с ячейкой, которая не является целым числом?" ) ПРИМЕЧАНИЕ. Этот тест проходит.

    [Fact]
    public void Validate_GivenNonNumericClaimantID_ReturnsInvalid()
    {
        int outint = 0;

        // Get a mock of a valid row
        var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();

        // change the TryGetValue result to false
        mockRow.Setup(m => m.Cell("B").TryGetValue(out outint)).Returns(false);

        var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);
        Assert.False(validationResult.IsValid);
        Assert.Equal("ClaimantID column value is not a number.", validationResult.Errors.First().ErrorMessage);
    }

Валидатор (с использованием FluentValidation):

public class InvoiceDetailsWorksheetRowValidator : AbstractValidator<IXLRow>
{
    public InvoiceDetailsWorksheetRowValidator()
    {
        RuleFor(x => x.Cell("B"))
            .Must(BeAnInt).WithMessage("ClaimantID column value is not a number.")
            .OverridePropertyName("ClaimantIDColumn");

    }

    private bool BeAnInt(IXLCell cellToCheck)
    {
        int result;
        var successful = cellToCheck.TryGetValue(out result);
        return successful;
    }
}

Для справки, метод из библиотеки:

    public Boolean TryGetValue<T>(out T value)
    {
        var currValue = Value;

        if (currValue == null)
        {
            value = default(T);
            return true;
        }

        bool b;
        if (TryGetTimeSpanValue(out value, currValue, out b)) return b;

        if (TryGetRichStringValue(out value)) return true;

        if (TryGetStringValue(out value, currValue)) return true;

        var strValue = currValue.ToString();
        if (typeof(T) == typeof(bool)) return TryGetBasicValue<T, bool>(out value, strValue, bool.TryParse);
        if (typeof(T) == typeof(sbyte)) return TryGetBasicValue<T, sbyte>(out value, strValue, sbyte.TryParse);
        if (typeof(T) == typeof(byte)) return TryGetBasicValue<T, byte>(out value, strValue, byte.TryParse);
        if (typeof(T) == typeof(short)) return TryGetBasicValue<T, short>(out value, strValue, short.TryParse);
        if (typeof(T) == typeof(ushort)) return TryGetBasicValue<T, ushort>(out value, strValue, ushort.TryParse);
        if (typeof(T) == typeof(int)) return TryGetBasicValue<T, int>(out value, strValue, int.TryParse);
        if (typeof(T) == typeof(uint)) return TryGetBasicValue<T, uint>(out value, strValue, uint.TryParse);
        if (typeof(T) == typeof(long)) return TryGetBasicValue<T, long>(out value, strValue, long.TryParse);
        if (typeof(T) == typeof(ulong)) return TryGetBasicValue<T, ulong>(out value, strValue, ulong.TryParse);
        if (typeof(T) == typeof(float)) return TryGetBasicValue<T, float>(out value, strValue, float.TryParse);
        if (typeof(T) == typeof(double)) return TryGetBasicValue<T, double>(out value, strValue, double.TryParse);
        if (typeof(T) == typeof(decimal)) return TryGetBasicValue<T, decimal>(out value, strValue, decimal.TryParse);

        if (typeof(T) == typeof(XLHyperlink))
        {
            XLHyperlink tmp = GetHyperlink();
            if (tmp != null)
            {
                value = (T)Convert.ChangeType(tmp, typeof(T));
                return true;
            }

            value = default(T);
            return false;
        }

        try
        {
            value = (T)Convert.ChangeType(currValue, typeof(T));
            return true;
        }
        catch
        {
            value = default(T);
            return false;
        }
    }

Проблема

Первый тест проходит. Но когда я запускаю этот тест, он терпит неудачу:

   [Fact]
   public void Validate_GivenNonNumericInvoiceNumber_ReturnsInvalid()
    {
        int outint = 0; // I don't care about this value

        // Get a mock of a valid worksheet row
        var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();

        mockRow.Setup(m => m.Cell("E").TryGetValue(out outint)).Returns(false);

        // Validates & asserts
        var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);
        Assert.False(validationResult.IsValid);

        // Placed here to ensure it the only error message. This is where it fails.
        Assert.Equal("InvoiceNumber column value is not a number.",validationResult.Errors.First().ErrorMessage);
    }

Но это не терпит неудачу, потому что проверка не была реализована - она ​​терпит неудачу, потому что другой элемент сначала недействителен, хотя я возвращаю его из получения действительного макета - тот же самый действительный макет, который проходит тесты в противном случае.

Это сообщение точно:

Ошибка Assert.Equal()

Позиция: первая разница в позиции 0

Ожидаемое: значение столбца InvoiceNumber не является числом.

Фактическое: значение столбца ClaimantID не является числом.

Я бы ожидал:

  • Он работает так же, как работал другой тест, или
  • Для счастливого пути также не удается.

Но когда проходит счастливый путь (например, действительный макет), но тест терпит неудачу, потому что метод недействителен (тот же самый, который проходит ту же проверку, что и часть "действительного" макета)... это оставляет меня полностью запутанным.

Для справки

  • Библиотека, которую я использую, ClosedXML
  • Библиотека проверки, которую я использую, FluentValidation
  • Я использую xUnit.NET до unit test.
4b9b3361

Ответ 1

Насколько мне нравится FluentValidator, это одна из проблем, которые я ненавижу, когда приходит на тестирование с помощью Fluent Validators.

Тест системы:

  public class InvoiceDetailsWorksheetRowValidator : AbstractValidator<IXLRow>
  {
      public InvoiceDetailsWorksheetRowValidator()
      {
        RuleFor(x => x.Cell("B"))
            .Must(BeAnInt).WithMessage("ClaimantID column value is not a number.")
            .OverridePropertyName("ClaimantIDColumn");

        RuleFor(x => x.Cell("E"))
            .Must(BeAnInt).WithMessage("InvoiceNumber column value is not a number.")
            .OverridePropertyName("ClaimantIDColumn");

      }

      private bool BeAnInt(IXLCell cellToCheck)
      {
        int result;
        var successful = cellToCheck.TryGetValue(out result);
        return successful;
      }
  }

Единичные тесты:

  [Fact]
  public void Validate_GivenNonNumericClaimantID_ReturnsInvalid()
  {
     int outint = 0;

     // Get a mock of a valid row
     //var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();
       var mockRow = new Mock<IXLRow>();

     // change the TryGetValue result to false
     mockRow.Setup(m => m.Cell("B").TryGetValue(out outint)).Returns(false);

     var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);

     Assert.True(validationResult.Errors.Any
       (x => x.ErrorMessage == "ClaimantID column value is not a number."));

     //Other option:
     //validationResult.Errors.Remove(validationResult.Errors.First(x => x.ErrorMessage == "InvoiceNumber column value is not a number."));
     //Assert.Equal("ClaimantID column value is not a number.", validationResult.Errors.First().ErrorMessage);
  }

 [Fact]
 public void Validate_GivenNonNumericInvoiceNumber_ReturnsInvalid()
 {
     int outint = 0; // I don't care about this value

     // Get a mock of a valid worksheet row
     var mockRow = new Mock<IXLRow>();
     mockRow.Setup(m => m.Cell("E").TryGetValue(out outint)).Returns(false);

     // Validates & asserts
     var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);

     Assert.True(validationResult.Errors.Any
          (x => x.ErrorMessage == "InvoiceNumber column value is not a number."));

     //Other option:
     //validationResult.Errors.Remove(validationResult.Errors.First(x => x.ErrorMessage == "ClaimantID column value is not a number."));
     //Assert.Equal("InvoiceNumber column value is not a number.", validationResult.Errors.First().ErrorMessage);
  }