Use AutoData and MemberData attributes in XUnit test
Asked Answered
W

2

18

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?

Welcome answered 29/6, 2016 at 1:0 Comment(0)
W
11

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.

Warrick answered 29/6, 2016 at 4:24 Comment(2)
I don't know. From what I'm seeing here, MemberData is just like PropertyData but it also supports static fields and static methods.Warrick
In this answer, PropertyData is combined with AutoNSubstituteData so it ought to work in xUnit.net 2.x with MemberData as well...Warrick
F
8

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)
    {
    }
}
Furriery answered 10/3, 2017 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.