Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use AutoData and MemberData attributes in XUnit test

I'm facing an interesting problem. I found out that the AutoDataAttribute can be use to minimize the "Arrange" part of your test (dependencies passed through the ctor). Awesome!

Example:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    { }
}

[Theory, AutoMoqData]
public void Process_ValidContext_CallsK2Workflows(
    [Frozen]Mock<IK2Datasource> k2,
    [Frozen]Mock<IAppConfiguration> config,
    PrBatchApproveBroker sut)
{
   (...)
}

Now i want to use this great feature and inject my own data into this theory:

[Theory, AutoMoqData, MemberData("Data")]
public void ExtractPayments_EmptyInvoiceNumber_IgnoresRecordsWithEmptyInvoiceNumber(
        [Frozen]Mock<IExcelDatasource> xls,
        SunSystemExcelDatasource sut,
        List<Row> rows,
        int expectedCount)
{
    (...)
}

Problem: AutoData attribute will generate random data for me. The only way I found is to get rid of the AutoData attribute and use MemberData. If I do that, I need to handle object instantiations myself :)...

Is there a way to pass my classes and some "hard-coded" data at the same time?

Thank u, Seb

like image 760
Seb Avatar asked Jun 29 '16 01:06

Seb


People also ask

What is difference between fact and Theory in xUnit?

The primary difference between fact and theory tests in xUnit is whether the test has any parameters. Theory tests take multiple different inputs and hold true for a particular set of data, whereas a Fact is always true, and tests invariant conditions.

What is InlineData in xUnit?

[InlineData] allows us to specify that a separate test is run on a particular Theory method for each instance of [InlineData] .

What class do you inherit from to create a custom attribute that provide test data to data driven tests?

Out of the box, you can use [InlineData] , [ClassData] , and [MemberData] classes to pass data to such a theory test. All of these attributes derive from DataAttribute , which you can also derive from to create your own custom data source.

What is AutoFixture xUnit?

AutoFixture is a library that you can use alongside your testing framework to reduce the amount of boilerplate test code you need to write and thus improve your productivity. At its core, AutoFixture helps you setup your tests by generating anonymous test data for you.


2 Answers

Is there a way to pass my classes and some "hard-coded" data at the same time?

One way of doing that is by supplying some inline values through the attribute, and have AutoFixture fill the rest of them.

[Theory, InlineAutoMoqData(3)]
public void ExtractPayments_EmptyInvoiceNumber_IgnoresRecordsWithEmptyInvoiceNumber(
    int expectedCount,
    [Frozen]Mock<IExcelDatasource> xls,
    SunSystemExcelDatasource sut,
    List<Row> rows)
{
    // expectedCount is 3.
}

Note that I had to move expectedCount in order to be the first parameter, and make use of a custom InlineAutoMoqData attribute defined as:

internal class AutoMoqDataAttribute : AutoDataAttribute
{
    internal AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}

internal class InlineAutoMoqDataAttribute : CompositeDataAttribute
{
    internal InlineAutoMoqDataAttribute(params object[] values)
        : base(
              new DataAttribute[] { 
                  new InlineDataAttribute(values),
                  new AutoMoqDataAttribute() })
    {
    }
}

See also this post and this one for some other examples.

like image 183
Nikos Baxevanis Avatar answered Oct 15 '22 11:10

Nikos Baxevanis


You will have to create your own custom DataAttribute. This is how you can compose.

/// <summary>
/// Helper DataAttribute to use the regular xUnit based DataAttributes with AutoFixture and Mocking capabilities.
/// </summary>
/// <seealso cref="Xunit.Sdk.DataAttribute" />
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class AutoCompositeDataAttribute : DataAttribute
{
    /// <summary>
    /// The attributes
    /// </summary>
    private readonly DataAttribute baseAttribute;

    /// <summary>
    /// The automatic data attribute
    /// </summary>
    private readonly DataAttribute autoDataAttribute;

    /// <summary>
    /// Initializes a new instance of the <see cref="AutoCompositeDataAttribute" /> class.
    /// </summary>
    /// <param name="baseAttribute">The base attribute.</param>
    /// <param name="autoDataAttribute">The automatic data attribute.</param>
    public AutoCompositeDataAttribute(DataAttribute baseAttribute, DataAttribute autoDataAttribute)
    {
        this.baseAttribute = baseAttribute;
        this.autoDataAttribute = autoDataAttribute;
    }

    /// <summary>
    /// Returns the data to be used to test the theory.
    /// </summary>
    /// <param name="testMethod">The method that is being tested</param>
    /// <returns>
    /// One or more sets of theory data. Each invocation of the test method
    /// is represented by a single object array.
    /// </returns>
    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        if (testMethod == null)
        {
            throw new ArgumentNullException(nameof(testMethod));
        }

        var data = this.baseAttribute.GetData(testMethod);

        foreach (var datum in data)
        {
            var autoData = this.autoDataAttribute.GetData(testMethod).ToArray()[0];

            for (var i = 0; i < datum.Length; i++)
            {
                autoData[i] = datum[i];
            }

            yield return autoData;
        }
    }
}



/// <summary>
/// Member auto data implementation based on InlineAutoDataAttribute and MemberData
/// </summary>
/// <seealso cref="Ploeh.AutoFixture.Xunit2.CompositeDataAttribute" />
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MemberAutoDataAttribute : AutoCompositeDataAttribute
{
    /// <summary>
    /// The automatic data attribute
    /// </summary>
    private readonly AutoDataAttribute autoDataAttribute;

    /// <summary>
    /// Initializes a new instance of the <see cref="MemberAutoDataAttribute" /> class.
    /// </summary>
    /// <param name="memberName">Name of the member.</param>
    /// <param name="parameters">The parameters.</param>
    public MemberAutoDataAttribute(string memberName, params object[] parameters)
        : this(new AutoDataAttribute(), memberName, parameters)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="MemberAutoDataAttribute" /> class.
    /// </summary>
    /// <param name="autoDataAttribute">The automatic data attribute.</param>
    /// <param name="memberName">Name of the member.</param>
    /// <param name="parameters">The parameters.</param>
    public MemberAutoDataAttribute(AutoDataAttribute autoDataAttribute, string memberName, params object[] parameters)
        : base((DataAttribute)new MemberDataAttribute(memberName, parameters), (DataAttribute)autoDataAttribute)
    {
        this.autoDataAttribute = autoDataAttribute;
    }

    /// <summary>
    /// Gets the automatic data attribute.
    /// </summary>
    /// <value>
    /// The automatic data attribute.
    /// </value>
    public AutoDataAttribute AutoDataAttribute => this.autoDataAttribute;
}

If you want to enable Moq, then extend this to get

/// <summary>
/// The member auto moq data attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MemberAutoMoqDataAttribute : MemberAutoDataAttribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MemberAutoMoqDataAttribute"/> class.
    /// </summary>
    /// <param name="memberName">Name of the member.</param>
    /// <param name="parameters">The parameters.</param>
    public MemberAutoMoqDataAttribute(string memberName, params object[] parameters)
        : base(new AutoMoqDataAttribute(), memberName, parameters)
    {
    }
}
like image 37
Kiran Madipally Avatar answered Oct 15 '22 11:10

Kiran Madipally