Question

I use AutoData in my xUnit unit tests. I occasionally have a need for a particular number of objects to be supplied to my tests. Consider the following class:

public class Recipient
{
    public void Receive(
        CallingBird bird1,
        CallingBird bird2,
        CallingBird bird3, 
        CallingBird bird4
        )
    {
        this.Bird1 = bird1;
        this.Bird2 = bird2;
        this.Bird3 = bird3;
        this.Bird4 = bird4;
    }

    public CallingBird Bird1 { get; private set; }
    public CallingBird Bird2 { get; private set; }
    public CallingBird Bird3 { get; private set; }
    public CallingBird Bird4 { get; private set; }
}

Without AutoData, I might write a test like this:

[Fact]
public void All_Birds_Are_Populated()
{
    var bird1 = new CallingBird();
    var bird2 = new CallingBird();
    var bird3 = new CallingBird();
    var bird4 = new CallingBird();
    var sut = new Recipient();

    sut.Receive(bird1, bird2, bird3, bird4);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

Using AutoData in situations like this, I've been asking for an array of arrays of the object I need in order to get enough distinct instances (assume I need distinct instances) like this:

[Theory, Autodata]
public void All_Birds_Are_Populated(CallingBird[][] birds, Recipient sut)
{
        sut.Receive(birds[0][0], birds[0][1], birds[0][2] ,birds[1][0]);

        Assert.NotNull(sut.Bird1);
        Assert.NotNull(sut.Bird2);
        Assert.NotNull(sut.Bird3);
        Assert.NotNull(sut.Bird4);
    }
}

When you ask for arrays from AutoData, it gives you an array of 3 of those objects. So, if I need 4 of something, I could ask for 2 arrays, or an array of arrays (as shown), which in this example is more wasteful than asking for two arrays. It works, but I'm often asking for more instances to be supplied than I need. Imagine a situation in which the count is higher, the objects are more expensive to create, etc.

Can you suggest a cleaner way to ask for N objects of a type as a unit test parameter, where N is exactly the number I need?

Was it helpful?

Solution 2

Here's a proposed answer based on comments from Mark Seemann so far. I'll modify this as appropriate if this wasn't what he was hinting at...

It seems I may have been overthinking things a bit. If I need 4 CallingBird instances for my SUT's method, then I can simply ask for those instances in separate parameters in the unit test signature like this:

[Theory, Autodata]
public void All_Birds_Are_Populated(
    CallingBird bird1,
    CallingBird bird2,
    CallingBird bird3,
    CallingBird bird4,
    Recipient sut)
{
    sut.Receive(bird1, bird2, bird3, bird4);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

If the parameter list gets too long, then it may be identifying a code smell in my SUT's method signature. If it's not a code smell, then I should be able to tolerate at least the same number of parameters in my test method as I do in my SUT's method.

I suppose I could ask for arrays in the test method like in the OP to save space, but that's probably at the expense of showing clear intent.

OTHER TIPS

Lumirris' own answer is the best answer, because it explains the learning and feedback opportunities provided by writing a unit test.

However, I'd like to provide an alternative, only for the sake of completeness, but I don't think this should be the accepted answer.

With AutoFixture, you can ask for a Generator<T>, which is a class that implements IEnumerable<T> by providing an infinite (lazily evaluated) sequence of elements. It enables you to take a finite, known number of elements:

[Theory, Autodata]
public void All_Birds_Are_Populated(
    Generator<CallingBird> g,
    Recipient sut)
{
    var birds = g.Take(4).ToList();

    sut.Receive(birds[0], birds[1], birds[2], birds[3]);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

If you just want stuff fed into a function and you don't care what it is, use Do (or Get):-

[Theory, AutoData]
public void All_Birds_Are_Populated( Recipient sut, IFixture fixture)
{
    // C# requires lots of disambiguation. Go read Eric Lippert/Jon Skeet/Tomas Petricek :)
    fixture.Do<CallingBird,CallingBird,CallingBird,CallingBird>( sut.Receive );

    Assert.NotNull( sut.Bird1);
    Assert.NotNull( sut.Bird2);
    Assert.NotNull( sut.Bird3);
    Assert.NotNull( sut.Bird4);
}

EDIT: If you need 5 arguments, obviously the best general advice is to listen to your tests. However, if my tests and I were agreeing to disagree, I probably write an assembly-local (Beware The Share)[http://www.amazon.com/Things-Every-Software-Architect-Should/dp/059652269X] extension method of Do that can synthesize 5 arguments.

I personally wouldn't run into either of these problems (having to over-specify types, not having tuples first class) with my toolchain -- I'd instead say:

[<Theory;AutoData>]
let ``All birds are populated`` (sut:Recipient) (fixture:IFixture) =
    sut.Receive |> fixture.Do

    test <@ sut.Bird1 <> null && sut.Bird2 <> null && sut.Bird3 <> null && sut.Bird4 <> null @>

OR

[<Theory;AutoData>]
let ``All birds are populated`` (sut:Recipient) args =
    sut.Receive args

    test <@ sut.Bird1 <> null && sut.Bird2 <> null && sut.Bird3 <> null && sut.Bird4 <> null @>

Found myself here after searching for how to use AutoData to setup an array of items instead of using multiple separate parameters in the test method. Following Mark Seeman's answer above I changed this approach...

[Theory, AutoData]
public void Test_Using_Separate_Parameters(SomeObject object1, SomeObject object2, SomeObject object3)
{
    var objects = new[] {object1, object2, object3};

    _mockProvider
        .Setup(x => x.ReturnSomeArray())
        .Returns(objects);
}

to this instead...

[Theory, AutoData]
public void Test_Using_Generator(Generator<SomeObject> objectGenerator)
{
    _mockProvider
        .Setup(x => x.ReturnSomeArray())
        .Returns(objectGenerator.Take(3).ToArray());
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top