7

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?

Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
Jeff
  • 2,191
  • 4
  • 30
  • 49
  • 1
    You have a SUT method with a *very specific* number of parameters, but you want your test method to provide you with a *general number* of instances. What does that tell you about the SUT's API? – Mark Seemann Jan 17 '14 at 21:25
  • Well, I actually want my test method to provide me with a *very specific* number of parameters also. Ordinarily, I might answer your question by saying that my SUT's API should be changed to have a single property of type `CallingBird[]`, but this is modeling something where a parent object has exactly 4 child properties of the same type, each with distinct values. I don't know how else to design that and satisfy that there are exactly 4 CallingBirds. – Jeff Jan 17 '14 at 21:32
  • 1
    So, you have that `Receive` method that you need to populate. It has a very particular *signature*. Now you want to use `[AutoData]` to give you the values for it. This means that you have to come up with an appropriate *signature* for the test method too. How could you model such a method signature to fit what you want to do? – Mark Seemann Jan 18 '14 at 12:12
  • Ah. Simply ask for the 4 `CallingBird` instances, each as their own test method argument, plus the `Recipient` SUT. Is that what you're hinting at? Makes perfect sense. I guess I was distilling the situation down a bit in my example above, but I was imagining a much larger number of instances required (10+), and how unwieldy that might get in the test method. However, I suppose that speaks to a potential problem in the SUT's method signature, and if it doesn't, then I should be able to tolerate the same signature length in my test method as I do in my SUT's method, right? – Jeff Jan 18 '14 at 19:14
  • 1
    That's exactly what I meant :) Sorry to be vague, but I really like if I can make you arrive at a solution for yourself :) Now for the alternatives (coming up)! – Mark Seemann Jan 18 '14 at 21:50
  • I appreciate having been given the hints to solve this ;) – Jeff Jan 18 '14 at 22:50

4 Answers4

10

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);
}
Community
  • 1
  • 1
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • This is the perfect answer if you are setting up a mock to actually return a collection of items - much cleaner than injecting each item as a separate test method parameter. – Ben Wesson Apr 09 '21 at 06:30
5

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.

Jeff
  • 2,191
  • 4
  • 30
  • 49
  • 1
    "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." Yes; this! – Mark Seemann Jan 18 '14 at 22:05
4

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 @>
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • This doesn't compile for me (`fixture.Do( sut.Receive);`) - can you help with the syntax? I haven't used `Do` or `Get` before, and it looks interesting. – Jeff Jan 18 '14 at 23:04
  • A-ha! I figured it out: `fixture.Do(sut.Receive);` Looks like the current implementation allows for a maximum of 4 types to be specified, so this wouldn't work with a larger number of parameters, would it? – Jeff Jan 18 '14 at 23:11
  • @Lumirris Sorry, rusty on that C# restriction as F# takes it in stride (it asks AF for a `Tuple\`4`, by doing approx `fixture.Do>( x => sut.Receive( x.Item1, x.Item2, x.Item3, x.Item4 ) )`. Your disambiguation suggestion is the cleanest way to it in C# (v5 anyway :D). The F# approach also handles >4 args cleanly too BTW. – Ruben Bartelink Jan 19 '14 at 00:53
  • which series by Eric Lippert? Or just a general pointer to his works? – Jeff Jan 19 '14 at 04:55
  • @Lumirris My memory and/or google-fu is failing me in answering your question. I thought I remembered a 5-ish part series which walked through the limits of type inference in C#. All I'm seeing is [this category](http://ericlippert.com/category/csharp/type-inference/). I'm no longer certain whether it's actually [C# in Depth](http://manning.com/skeet3/) or [Real World Functional Programming in F# and C#](http://manning.com/petricek/) now. Bottom line is that for lots of reasons the type inference in C# isn't worthy of the term compared to F# (and obviously others such as Haskell etc.) – Ruben Bartelink Jan 19 '14 at 12:15
0

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());
}
Ben Wesson
  • 589
  • 6
  • 16