2012-04-05 2 views
6

Я ищу, чтобы настроить поведение времени создания AutoFixture таким образом, что я могу настроить некоторые зависимые объекты после того, как свойства объекта были сгенерированы и назначены.Может ли AutoFixture выполнить делегат во время создания объекта?

Например, предположим, что у меня есть метод, который настраивает User, поскольку его свойство IsDeleted всегда должно быть ложным для определенного набора тестов:

public class User 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public bool IsDeleted { get; set; } 
} 

public static ObjectBuilder<User> BuildUser(this Fixture f) 
{ 
    return f.Build<User>().With(u => u.IsDeleted, false); 
} 

(сдаю ObjectBuilder обратно к испытанию, так это можете дополнительно настроить прибор, если это необходимо.)

То, что я хотел бы сделать, автоматически ассоциирует пользователя с анонимной коллекцией по его Id во время создания, но я не могу сделать это как есть, потому что Id не имеет был создан к тому моменту, когда я возвращаемое значение возвращается к собственному тесту. Вот что я делаю:

public static ObjectBuilder<User> BuildUserIn(this Fixture f, UserCollection uc) 
{ 
    return f.Build<User>() 
      .With(u => u.IsDeleted, false); 
      .AfterCreation(u => 
      { 
       var relation = f.Build<UserCollectionMembership>() 
           .With(ucm => ucm.UserCollectionId, uc.Id) 
           .With(ucm => ucm.UserId, u.Id) 
           .CreateAnonymous(); 
       Repository.Install(relation); 
      } 
} 

Возможно ли это? Или, может быть, есть лучший способ выполнить мою задачу создания анонимного графа объектов?

+0

Вы хотите иметь конкретный экземпляр для типа пользователя и повторно использовать его значение свойства Id в другом месте? –

+2

Помогает ли это? http://stackoverflow.com/questions/5398258/customizing-autofixture-builder-with-seeded-property/5398653#5398653 –

+0

@MarkSeemann: 'Do()' может, очевидно, выполняться до того, как объект будет полностью заполнен, так что doesn ' т работы. Я могу (и делать) вручную выполнить пример lambda или выполнить настройку моего объекта после создания, но мне бы понравилась альтернатива, подобная предыдущей! – ladenedge

ответ

6

Для метода Build это невозможно, и, вероятно, никогда не будет, потому что есть гораздо лучшие варианты.

Прежде всего, никогда не нужно писать статические вспомогательные методы вокруг метода Build. Метод Build предназначен для действительно разовых инициализаций, где необходимо определить свойства или значения полей перед фактом.

I.e. Представьте себе класс, как это:

public class MyClass 
{ 
    private string txt; 

    public string SomeWeirdText 
    { 
     get { return this.txt; } 
     set 
     { 
      if (value != "bar") 
       throw new ArgumentException(); 
      this.txt = value; 
     } 
    } 
} 

В этом (надуманный) Например, прямо fixture.CreateAnonymous<MyClass> собирается бросить, потому что это будет пытаться присвоить что-то другое, чем «бар» в собственность.

В одноразовом сценарии для устранения этой проблемы можно использовать метод Build. Одним из примеров является просто установить значение явно «бар»:

var mc = 
    fixture.Build<MyClass>().With(x => x.SomeWeirdText, "bar").CreateAnonymous(); 

Однако еще проще было бы просто опустить эту собственность:

var mc = 
    fixture.Build<MyClass>().Without(x => x.SomeWeirdText).CreateAnonymous(); 

Однако, как только вы начинаете хотеть сделать это несколько раз , есть лучшие варианты. AutoFixture имеет очень сложный и настраиваемый механизм определения того, как создаются вещи.

В начале, можно было бы начать с перемещением упущение имущества в настройки, например:

fixture.Customize<MyClass>(c => c.Without(x => x.SomeWeirdText)); 

Теперь, когда прибор создает экземпляр MyClass, это только собирается пропустить эту недвижимость в целом. Вы все еще можете присвоить значение впоследствии:

var mc = fixture.CreateAnonymous<MyClass>(); 
my.SomeWeirdText = "bar"; 

Если вы хотите что-то более сложное, вы можете implement a custom ISpecimenBuilder. Если вы хотите запустить какой-то пользовательский код после создания экземпляра, вы можете украсить свой собственный ISpecimenBuilder Postprocessor и предоставить делегата.Это может выглядеть примерно так:

fixture.Customizations.Add(
    new Postprocessor(yourCustomSpecimenBuilder, obj => 
     { */ do something to obj here */ })); 

(? Кстати, вы все еще на AutoFixture 1.0 IIRC, не было в ObjectBuilder<T> вокруг с тех пор ...)

+0

Добавление постпроцессора может быть только билетом, и я ценю совет по правильному использованию AF. Спасибо! (Относительно v1.0, да: у нас была [проблема] (http://stackoverflow.com/questions/4650424/autofixture-2-with-isnt-working-as-it-did-in-autofixture-1) [ обновление] (http://stackoverflow.com/questions/8595498/why-isnt-autofixture-working-with-the-stringlength-data-annotation) - может быть, в третий раз это будет очарование!) – ladenedge

3

Там в useful discussion on this topic on the AutoFixture CodePlex site.

Я считаю, что мой postprocessor Customization, связанный там, должен вам помочь. Пример использования:

class AutoControllerDataAttribute : AutoDataAttribute 
{ 
    public AutoControllerDataAttribute() 
     : this(new Fixture()) 
    { 
    } 

    public AutoControllerDataAttribute(IFixture fixture) 
     : base(fixture) 
    { 
     fixture.Customize(new AutoMoqCustomization()); 
     fixture.Customize(new ApplyControllerContextCustomization()); 
    } 

    class ApplyControllerContextCustomization : PostProcessWhereIsACustomization<Controller> 
    { 
     public ApplyControllerContextCustomization() 
      : base(PostProcess) 
     { 
     } 

     static void PostProcess(Controller controller) 
     { 
      controller.FakeControllerContext(); 
      // etc. - add stuff you want to happen after the instance has been created