Search code examples
c#unit-testingxunit

xUnit and multiple data records for a test


I'm fairly new to Unit Testing and have the following code:

public class PowerOf
{
    public int CalcPowerOf(int @base, int exponent) {
        if (@base == 0) { return 0; }
        if (exponent == 0) { return 1; }
        return @base * CalcPowerOf(@base, exponent - 1);
    }
}

The unit test (with xUnit) I wrote for it first was this one, but I'm not quite sure if it's the right approach, or if I should use another pattern? What I wanted to know is whether this is the correct usage for passing multiple sets of data into a "unit test" - as I didn't see any docs or reference examples on xUnit's docs?

    [Fact]
    public void PowerOfTest() {
        foreach(var td in PowerOfTestData()) {
           Assert.Equal(expected, CalcPowerOf(@base, exponent));
        }
    }

    public class TestData {
      int Base {get;set;}
      int Exponent {get;set;}
      int ExpectedResult {get;set;}
    }

    public List<TestData> PowerOfTestData() {
        yield return new TestData { Base = 0, Exponent = 0, TestData = 0 };
        yield return new TestData { Base = 0, Exponent = 1, TestData = 0 };
        yield return new TestData { Base = 2, Exponent = 0, TestData = 1 };
        yield return new TestData { Base = 2, Exponent = 1, TestData = 2 };
        yield return new TestData { Base = 5, Exponent = 2, TestData = 25 };
    }

Solution

  • You'd be better of using a specialised construct in xUnit, called a Theory, that handles so called "Data Driven Tests". Decorate your testmethod with the Theory attribute and then make sure to return a static "member" with input parameters and the expected result as you already kind of did with the TestData class. See the example below, and ref to the xUnit documentation: "Writing your first theory".

    I would thus refactor your code like below. Firstly decorating the test with the attributes Theory and MemberData and adding parameters to your test "@base", "exponent" and "expectedResult" - as you had in your TestData class. xUnit won't allow you to use the TestData class, it only accepts an IEnumerable<object> and requires it to be static, but the benefit to a foreach loop construct is that all the tests are ran seperately. And for each run with a specific data set you'll get a green or red flag!

    public class PowerOfTests
    {
        [Theory]
        [MemberData(nameof(PowerOfTestData))]
        public void PowerOfTest(int @base, int exponent, int expected) {
            Assert.Equal(expected, CalcPowerOf(@base, exponent));
        }
    
        public static IEnumerable<object[]> PowerOfTestData() {
            yield return new object[] { 0, 0, 0 };
            yield return new object[] { 0, 1, 0 };
            yield return new object[] { 2, 0, 1 };
            yield return new object[] { 2, 1, 2 };
            yield return new object[] { 2, 2, 4 };
            yield return new object[] { 5, 2, 25 };
            yield return new object[] { 5, 3, 125 };
            yield return new object[] { 5, 4, 625 };
        }
    }