0

I'm unit testing the following method and I expect ArgumentOutOfRangeException to be thrown. Instead gridTrading.GridLevels().As<double[]>() returns null for whatever reason. It basically swallows the exception. How do I fix it by keeping the return type as IEnumerable<double> and not converting it to List<double>? Btw, I added .As<double[]>() to prevent multiple enumeration.

Lastly, do you think I'm wrong by trying to keep it IEnumerable<double>?

[Fact]
public void GridLevels_ShouldThrow_WhenGivenInvalidGridType()
{
    // Arrange
    const double lowerLimit = 2000;
    const double upperLimit = 10000;
    const int gridCount = 4;
    const int gridType = 4;

    var gridTrading = new GridTrading(lowerLimit, upperLimit, gridCount, (GridType)gridType);

    // Act
    var action = new Action(() => gridTrading.GridLevels().As<double[]>());

    // Assert
    action.Should().Throw<ArgumentOutOfRangeException>().WithMessage("Grid type must be valid");
}

// The method I test
public IEnumerable<double> GridLevels()
{
    switch (_gridType)
    {
        case GridType.Arithmetic:
            foreach (var level in CalculateArithmetic())
            {
                yield return level;
            }

            break;

        case GridType.Geometric:
            foreach (var level in CalculateGeometric())
            {
                yield return level;
            }

            break;

        default:
            throw new ArgumentOutOfRangeException(nameof(_gridType), "Grid type must be valid");
    }

    IEnumerable<double> CalculateArithmetic()
    {
        var step = (_upperLimit - _lowerLimit) / _gridCount;

        for (var i = 0; i <= _gridCount; i++)
        {
            var price = _lowerLimit + step * i;
            yield return price;
        }
    }

    IEnumerable<double> CalculateGeometric()
    {
        var step = Math.Pow(_upperLimit / _lowerLimit, 1.0 / _gridCount);

        for (var i = 0; i <= _gridCount; i++)
        {
            var price = _lowerLimit * Math.Pow(step, i);
            yield return price;
        }
    }
}

I know I could make GridLevels a List<double> instead IEnumerable<double> which would make it throw for real but I don't wanna do that.

public List<double> GridLevels()
{
    var list = new List<double>();

    switch (_gridType)
    {
        case GridType.Arithmetic:
            list.AddRange(CalculateArithmetic());
            break;

        case GridType.Geometric:
            list.AddRange(CalculateGeometric());
            break;

        default:
            throw new ArgumentOutOfRangeException(nameof(_gridType), "Grid type must be valid");
    }

    return list;

    IEnumerable<double> CalculateArithmetic()
    {
        var step = (_upperLimit - _lowerLimit) / _gridCount;

        for (var i = 0; i <= _gridCount; i++)
        {
            var price = _lowerLimit + step * i;
            yield return price;
        }
    }

    IEnumerable<double> CalculateGeometric()
    {
        var step = Math.Pow(_upperLimit / _lowerLimit, 1.0 / _gridCount);

        for (var i = 0; i <= _gridCount; i++)
        {
            var price = _lowerLimit * Math.Pow(step, i);
            yield return price;
        }
    }
}
nop
  • 4,711
  • 6
  • 32
  • 93
  • Which `As` method are you calling? I can't find one in the "usual suspects" of extension methods offered by [`Enumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-7.0). It's name certainly doesn't suggest to *me* that it would prevent multiple enumeration but since I can't find any documentation on it it's tricky to be sure... – Damien_The_Unbeliever Oct 26 '22 at 06:08
  • @Damien_The_Unbeliever, oh sorry, it's from FluentAssertions. I forgot to say. I'm basically using xUnit + FluentAssertions. – nop Oct 26 '22 at 06:14
  • Can't reproduce. Using `private static IEnumerable DummyMethod() => throw new ArgumentOutOfRangeException("test");`, the `.Should.Throw()` method executes correctly and continues on to the next line. – ProgrammingLlama Oct 26 '22 at 06:30
  • I would expect it to fail with an `XunitException` stating "Xunit.Sdk.XunitException: 'Expected exception message to match the equivalent of "Grid type must be valid", but "Grid type must be valid(Parameter '_gridType')" does not.". – ProgrammingLlama Oct 26 '22 at 06:37
  • 1
    If I remove the exception from `DummyMethod` and return an array, I get the following `XunitException`: "XunitException: 'Expected a to be thrown, but no exception was thrown.'" – ProgrammingLlama Oct 26 '22 at 06:37
  • @ProgrammingLlama, exactly. – nop Oct 26 '22 at 07:00

1 Answers1

2

You're GridLevels method returns a lazy-evaluated IEnumerable<double> which FA will never explicitly enumerate unless you use the Enumerating extension methods. Instead, you're using the As extension method. That one will try to cast your enumerable to double[], which isn't possible. In that case, As will return default(double[]). In other words, your code isn't being executed.

Dennis Doomen
  • 8,368
  • 1
  • 32
  • 44
  • Would it be possible for you to link to some documentation on that `As` method? When the OP mentioned it was from FA I tried to find it in the FA documentation site but it doesn't seem easy to find. – Damien_The_Unbeliever Oct 26 '22 at 07:11
  • It's so old, there isn't any... https://github.com/fluentassertions/fluentassertions/blob/1275c2612d24b8903c0b94db750a95d38a919607/Src/FluentAssertions/AssertionExtensions.cs#L945 – Dennis Doomen Oct 26 '22 at 11:03