title: SpecimenBuilder for a collection
body: <p>I have a need for customizing creation of a collection, with quite complicated relationships between the objects within it, and I can't figure out how to do it correctly.</p>
  
  <p>For the sake of this issue, let's assume I'm working on a todo app. It has <code>Item</code>s and <code>SubItem</code>s, and the items have a week number indicating when they should be done:</p>
  
  <pre><code>public class Item {
      public string Name { get; set; }
      public int Week { get; set; }
      public ICollection&lt;SubItem&gt; SubItems { get; set; }
  }
  
  public class SubItem {
      public string Name { get; set; }
      public Item Parent { get; set; }
  }
  </code></pre>
  
  <p>Now, because this is what data usually looks like in the actual application, I want to create a collection of Items that has the following properties:</p>
  
  <ul>
  <li>There are items that have the same name, but different weeks</li>
  <li>There are items that have the same week but different name</li>
  <li>There are sub-items that have the same name, but different parents</li>
  </ul>
  
  <p>In order to do this, I've created a <code>TodoItemSpecimenBuilder : ISpecimenBuilder</code> which starts its Create method like this:</p>
  
  <pre><code>var type = (request as PropertyInfo)?.PropertyType ?? request as Type;
  if (type == null || !typeof(IEnumerable&lt;Item&gt;).IsAssignableFrom(type))
  {
      return new NoSpecimen();
  }
  
  // build up the actual collection
  return BuildActualCollection();
  </code></pre>
  
  <p>However, when I run tests with this specimen builder included in my context, I get lots (maybe 20 or 30) hits on the return statement before I enter even my setup code, and the first time I try to actually <code>CreateMany&lt;Item&gt;()</code>, it blows up with a cast exception because it can't cast <code>OmitSpecimen</code> to <code>Item</code>.</p>
  
  <p>What am I doing wrong here?</p>
  
  <hr>
  
  <p>Full sample code, compilable after installing NUnit and AutoFixture:</p>
  
  <pre><code>public class TodoList
  {
      public ICollection&lt;Item&gt; Tasks { get; set; }
  }
  
  public class Item
  {
      public string Name { get; set; }
      public Week Week { get; set; }
      public ICollection&lt;SubItem&gt; SubItems { get; set; }
      public int ItemId { get; set; }
      public TodoList TodoList { get; set; }
  }
  
  public class SubItem
  {
      public Item Item { get; set; }
      public string Name { get; set; }
      public int SortOrder { get; set; }
      public string HelpText { get; set; }
  }
  
  public class Week
  {
      public int WeekId { get; set; }
  }
  
  public class ItemCollectionSpecimenBuilder : ISpecimenBuilder
  {
      public object Create(object request, ISpecimenContext context)
      {
          if (!IsApplicable(request))
          {
              return new NoSpecimen();
          }
  
          var items = new List&lt;Item&gt;(3);
          var week1 = context.Create&lt;Week&gt;();
          var week2 = context.Create&lt;Week&gt;();
  
          items.Add(CreateItem(context, week1));
          items.Add(CreateItem(context, week1));
          items.Add(CreateItem(context, week2));
  
          items.GroupBy(t =&gt; t.Week).ToList().ForEach(ConfigureNames);
          ConfigureSubItems(context, items);
  
          return items;
      }
  
      private static bool IsApplicable(object request)
      {
          bool IsManyItemsType(Type type) =&gt; typeof(IEnumerable&lt;Item&gt;).IsAssignableFrom(type);
          bool IsItemsType(Type type) =&gt; type != null &amp;&amp; typeof(Item) == type;
  
          switch (request)
          {
              case PropertyInfo pInfo:
                  return IsManyItemsType(pInfo.PropertyType);
              case Type type:
                  return IsManyItemsType(type);
              case MultipleRequest multipleRequest:
                  if (!(multipleRequest.Request is SeededRequest seededRequest))
                  {
                      return false;
                  }
                  return IsItemsType(seededRequest.Request as Type);
              default:
                  return false;
          }
      }
  
      private static Item CreateItem(ISpecimenContext context, Week week)
      {
          var item = context.Create&lt;Item&gt;();
          item.Week = week;
          return item;
      }
  
      private static void ConfigureNames(IEnumerable&lt;Item&gt; items)
      {
          string name = null;
          foreach (var item in items)
          {
              if (name == null)
              {
                  name = item.Name;
              }
              else
              {
                  item.Name = name;
              }
          }
      }
  
      private static void ConfigureSubItems(ISpecimenContext context, IEnumerable&lt;Item&gt; items)
      {
          foreach (var group in items.GroupBy(item =&gt; item.Week.WeekId))
          {
              var subItemTemplates = context.CreateMany&lt;SubItem&gt;().ToList();
              foreach (var item in group)
              {
                  item.SubItems.Clear();
                  foreach (var subItem in context.CreateMany&lt;SubItem&gt;().Zip(subItemTemplates,
                      (model, subItem) =&gt;
                      {
                          subItem.Item = item;
                          subItem.Name = model.Name;
                          subItem.SortOrder = model.SortOrder;
                          subItem.HelpText = model.HelpText;
                          return subItem;
                      }))
                  {
                      item.SubItems.Add(subItem);
                  }
              }
          }
      }
  }
  
  [TestFixture]
  public class AutoFixtureSpecimenBuilderTests
  {
      private static void TestCreationOfTasks(Func&lt;IFixture, ICollection&lt;Item&gt;&gt; creator)
      {
          var fixture = new Fixture();
          fixture.Customizations.Add(new ItemCollectionSpecimenBuilder());
          fixture.Behaviors.OfType&lt;ThrowingRecursionBehavior&gt;().ToList()
              .ForEach(b =&gt; fixture.Behaviors.Remove(b));
          fixture.Behaviors.Add(new OmitOnRecursionBehavior());
  
          var tasks = creator(fixture);
  
          Assert.AreEqual(3, tasks.Count);
          Assert.AreEqual(2, tasks.GroupBy(t =&gt; t.Week).Count());
          Assert.IsTrue(tasks.GroupBy(t =&gt; t.Week).Select(g =&gt; g.Select(t =&gt; t.Name).Distinct()).All(distinctNames =&gt; distinctNames.Count() == 1));
          var task = tasks.GroupBy(t =&gt; t.Week).OrderBy(g =&gt; g.Count()).First().OrderBy(t =&gt; t.ItemId).First();
  
      }
  
      [Test]
      public void CreateMany() =&gt; TestCreationOfTasks(fixture =&gt; fixture.CreateMany&lt;Item&gt;().ToList());
  
      [Test]
      public void CreateWithProperty() =&gt; TestCreationOfTasks(fixture =&gt; fixture.Create&lt;TodoList&gt;().Tasks);
  
      [Test]
      public void CreateAsList() =&gt; TestCreationOfTasks(fixture =&gt; fixture.Create&lt;IList&lt;Item&gt;&gt;());
  }
  </code></pre>