Bob Horn
Bob Horn

Reputation: 34325

How can I pass values to xUnit tests that accept a nullable decimal?

One of my unit tests has this signature:

public void FooWithFilter(string fooId, decimal? amount)

When I test it with null, it works:

[InlineData("123", null)]

But if I use an actual value, such as:

[InlineData("123", 610)]

I get an error:

System.ArgumentException Object of type 'System.Int32' cannot be 
converted to type 'System.Nullable`1[System.Decimal]'.

I tried using 610M as the attribute value, but that's not allowed as an attribute value:

An attribute argument must be a constant expression, type of expression
or array creation expression of an attribute parameter type.

Is there a way to use a nullable decimal here?

Upvotes: 21

Views: 13992

Answers (6)

Maico
Maico

Reputation: 729

I'm using xunit 2.6.4 and I'm still getting that same error. I tried some suggestions from other answers from this question without success.

I avoided using the MemberDataAttribute doing this workaround:

[Theory]
[InlineData(null)]
[InlineData(42)]
public void MyTest(object testValueObj)
{
   decimal? testValue = testValueObj is null ? null : Convert.ToDecimal(testValueObj);
       ...
}

Upvotes: 0

Rolf Kristensen
Rolf Kristensen

Reputation: 19877

Had this unit-test:

[Theory]
[InlineData(null)]
[InlineData(42)]
public void MyTest(long? testValue)
{
   ...
}

And when upgrading from xUnit 2.4.0 then it failed with:

System.ArgumentException : Object of type 'System.Int32' cannot be converted to type 'System.Nullable`1[System.Int64]'

The solution was to explicit specify long-value by adding suffix L:

[Theory]
[InlineData(null)]
[InlineData(42L)]
public void MyTest(long? testValue)
{
   ...
}

For decimal values then one just need to add suffix m. Ex. 42m.

Alternative then I could have used IConvertible testValue and called testValue?.ToInt64(null) or testValue?.ToDecimal(null).

Upvotes: 0

Ash K
Ash K

Reputation: 3701

There's also MemberData if you don't want to create a new class.

For eg:

[Theory]
[MemberData(nameof(DoSomethingTestData))]
public void Should_Do_Something(string someStr, decimal? someDecimal, decimal expectedDecimal)
{
    // Arrange
    // Act
    // Assert
}

public static IEnumerable<object[]> DoSomethingTestData()
{
    var testData = new List<object[]>
    {
        new object[] { "SomeString", 1M, 2M },
        new object[] { "SomeOtherString", null, 1M } // etc.
    };

    return testData;
}

Upvotes: 3

JLRishe
JLRishe

Reputation: 101748

As indicated in the comments, you can't use a decimal here because decimal is not one of the types that's allowed in attribute parameter values.

However, xUnit provides a more flexible way to pass parameter values to test methods, using ClassData:

[Theory]
[ClassData(typeof(FooDataGenerator))]
public void FooWithFilter(string fooId, decimal? amount)

To use this, you simply need to define a class that extends IEnumerable<object[]> and produces the input values you want:

public class FooDataGenerator : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] {"123", null},
        new object[] {"123", 610M}
    };

    public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

Some further references on the various ways of passing values to xUnit tests:
Creating Parameterised tests in xUnit
xUnit Theory: Working With InlineData, MemberData, ClassData

Upvotes: 19

Matthias G&#252;ntert
Matthias G&#252;ntert

Reputation: 4668

In addition to JLRishe's answer, I would suggest to inherit from TheoryData or its generic counterparty TheoryData<> so we get strongly typed test data and cleaner code. I find the documentation somehow thin in that respect.

[Theory]
[ClassData(typeof(FooDataGenerator))]
public void FooWithFilter(string fooId, decimal? expected)
{
    // Arrange // Act // Assert
}

public class FooDataGenerator : TheoryData<string, decimal?>
{
    public FooDataGenerator()
    {
        this.Add("987", null);
        this.Add("123", 610m);
        // ...
    }
}

Upvotes: 11

A simple work arround for this problem is set the parameters double? and cast them to decimal? like so

[Theory]
        [InlineData(1200, 1150, 50, 100)]
        [InlineData(1200.50, 1200.50, 0, 0)]
        [InlineData(1250.50, 1150, 0, 100.50)]
        [InlineData(1150, 1150, null, null)]
        [InlineData(0, null, null, null)]
        [InlineData(-50, null, 50, null)]
        [InlineData(50, null, null, 50)]

        public void SubTotal(double valorEsperado, double? valorTotal, double? valorFrete, double? valorDesconto) =>
            Assert.Equal((decimal)valorEsperado, PedidoBuilderTest.New
                                            .AtribuirValores((decimal?)valorTotal, (decimal?)valorFrete, (decimal?)valorDesconto)
                                            .Build().SubTotal);

Upvotes: 10

Related Questions