Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use AutoData in unit tests to supply N objects of a type in test arguments?

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?

like image 839
Jeff Avatar asked Jan 17 '14 21:01

Jeff


2 Answers

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);
}
like image 162
Mark Seemann Avatar answered Sep 30 '22 18:09

Mark Seemann


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.

like image 26
Jeff Avatar answered Sep 30 '22 17:09

Jeff