Can I pass a deligate to a Xunit Theory

Viewed 680

I'm interested in reusing a test theory on a number of classes, specifically a number of constructors that need to the same tests. I initially had the idea of using a delegate to carry out this functionality.

However, I think I'm probably trying to reinvent the wheel and although C# has some functional capability I think I'm attempting an improper method. Is there a supported method for this kind of thing using something more correct than InlineData.

InlineData seems to be for inject the input so I might test many examples of a given test. But can I supply several variables to several methods and get testing ^x not *x

[Theory]
[InlineData(input => new ConcreteConstructor(input)) ]
public void Constructor_Should_Not_Throw_Expection (Action<string>)
{
  constructor("SomeString");            
}

N.B I think I should be using Func in this case as an object is returned. Anyhow I suspect it's completely the wrong approach so it's not the main consideration.

3 Answers
public static IEnumerable<object[]> TestData()
{
  Action<string> a1 = input => new ConcreteConstructor(input);
  yield return new object[] { a1 };
  Action<string> a2 = input => new AnotherConstructor(input);
  yield return new object[] { a2 };
}

[Theory]
[MemberData(nameof(TestData))]
public void Constructor_Should_Not_Throw_Expection(Action<string> constructor)
{
  constructor("SomeString");
}

Thanks to Yevheniy Tymchishin for his answer and I would like to add additional way, just in case if someone want to add delagate without declaring a new object outside the yield return.

static IEnumerable<object[]> Data
{
    get 
    {
        //Yevheniy's approach
        Action<string> a1 = input => Construct(input);
        yield return new object[] { a1 };

        //new approach with hidden parameter
        yield return new object[] {new Action<string> (Construct)};
        
        //same but with exposed parameter syntax
        yield return new object[] {new Action<string> ((input) => Construct(input))};
    }
}

All three of them do exactly same thing, just in a different syntax, same logic would apply to any other delegate type(func or whatever)

One solution with inheritance of a common abstract class:

abstract public class CommonTest_ConstructorShould
{   
        public abstract void Constructor(string input);

        [Fact]
        public void Constructor_Should_Not_Throw_Expection()
        {
            Constructor(System.IO.Directory.GetCurrentDirectory());
        }

        [Fact]
        public void Constructor_Should_Throw_Exception()
        {
            Assert.Throws<ArgumentException>(() => Constructor("/PPPPPPPPPPPPPPPPPPPPPPP"));
        }
    }

    public class LunarCalendarClient_ConstructorShould : LocalJsonClient_ConstructorShould
    {   
        override public void Constructor(string input){
            new ConcreteClass(input);
        }
    }
Related