Ken B
Ken B

Reputation: 99

Trying to create a xunit test for DBNull

I am trying to create a xunit test for a method that checks for DBNull.Value. I am currently using InlineData to create the test but it is giving an error when attempting to use 'DBNull.Value' as my test param.

        [Theory]
        [InlineData(null)]
        [InlineData(DBNull.Value)]
        public void GetStringorNullNegativeTest(object value)
        {
            //arrange, act
            var _resultString = value == null ? "" : value.ToString().Trim();
            var _returnString = value.GetStringorNull();

            //assert
            Assert.NotEqual(_resultString, _returnString);
        }

    public static string GetStringorNull(this System.Object o)
    {
        if (o == null || o == DBNull.Value)
            return null;

        if (o is string)
        {
            string s = (string)o;
            if (string.IsNullOrWhiteSpace(s))
                return null;
            else
                return s.Trim();
        }

        return o.ToString().Trim();
    }

Is there a simple way to check for 'DBNull.Value' in the test without completely refactoring the test?

Upvotes: 0

Views: 343

Answers (3)

kipras
kipras

Reputation: 89

The following does require changing the way you're handling the test itself from InlineData to MemberData. Make your GetStringorNullNegativeTest test look like below, adding an additional MemberData method to facilitate any future tests too.

Similar to what Ken B was mentioning. Dai added an asterisks to understand the difference between the two types of test

What you have is the MemberData can run multiple tests, and more features than the InlineData restrictions of attributes. I added your null test as well.

        [Theory]
        [MemberData(nameof(GetStringorNullNegativeTest_MemberData))]
        public void GetStringorNullNegativeTest(object value)
        {
            //arrange, act
            var _resultString = value == null ? "" : value.ToString().Trim();
            var _returnString = value.GetStringorNull();

            //assert
            Assert.NotEqual(_resultString, _returnString);
        }

        public static IEnumerable<object[]> GetStringorNullNegativeTest_MemberData()
        {
            yield return new object[] { null };
            yield return new object[] { DBNull.Value };
        }

Upvotes: 0

Ken B
Ken B

Reputation: 99

I ended up using the MemberData option as suggested. Changed to the below and it works.

        public static IEnumerable<object[]> DBNullObject =>
            new List<object[]>
            {
                new object[] { DBNull.Value }
            };

        [Theory]
        [MemberData(nameof(DBNullObject))]
        public void GetStringorNullNegativeTest(object value)
        {
            //arrange, act
            var _resultString = value == null ? "" : value.ToString().Trim();
            var _returnString = value.GetStringorNull();

            //assert
            Assert.NotEqual(_resultString, _returnString);
        }

Upvotes: 2

Dai
Dai

Reputation: 155428

  • C# only allows const expressions to be used as values for attributes, namely:
    • String literals (including nameof() expressions).
    • The null constant reference.
    • Integer and Float (Single and Double) literals.
      • ...but not Decimal 0M literals.
    • enum members.
    • Other const.
    • typeof() expressions.
    • (and a few others I'm probably forgetting)
  • Basically, with the exception of String values, reference-types cannot be used in attribute use-sites.
  • DBNull is a class, i.e. a reference-type.
  • DBNull.Value is a public static readonly field, not a const field.
  • Therefore DBNull.Value cannot be used in an attribute use-site directly.

Instead, use some other means of representing a DBNull... for example:

public enum GetStringOrNullTestMode
{
    UseValue,
    UseDBNull
}

[Theory]
[InlineData(GetStringOrNullTestMode.UseValue, null)]
[InlineData(GetStringOrNullTestMode.UseValue, "")]
[InlineData(GetStringOrNullTestMode.UseValue, "abc123")]
[InlineData(GetStringOrNullTestMode.UseDBNull, null)]
public void GetStringorNullNegativeTest( GetStringOrNullTestMode mode, Object? value )
{
    if( mode == GetStringOrNullTestMode.UseDBNull ) value = DBNull.Value;

    // arrange, act
    var actual = value == null ? "" : value.ToString().Trim();
    var returnString = value.GetStringorNull();

    // assert
    Assert.NotEqual_resultString, _returnString);
}

Another approach is to use hand-written [Fact] methods that create and pass non-const values into the same orignal [Theory] test method, e.g.:

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("abc123")]
public void GetStringorNullNegativeTest( Object? value )
{
    if( mode == GetStringOrNullTestMode.UseDBNull ) value = DBNull.Value;

    // arrange, act
    var actual = value == null ? "" : value.ToString().Trim();
    var returnString = value.GetStringorNull();

    // assert
    Assert.NotEqual_resultString, _returnString);
}

[Fact]
public void GetStringorNullNegativeTestWithDBNull()
{
    this.GetStringorNullNegativeTest( value: DBNull.Value );
}

Upvotes: 2

Related Questions