Skip to content

Instantly share code, notes, and snippets.

@Rookian
Created January 7, 2012 18:29
Show Gist options
  • Save Rookian/1575568 to your computer and use it in GitHub Desktop.
Save Rookian/1575568 to your computer and use it in GitHub Desktop.
The current proxy generator can not intercept the specified method for the following reason: - Sealed methods can not be intercepted.
public class UnitOfWork : IUnitOfWork
{
private ObjectContext _db;
public UnitOfWork(ObjectContext db)
{
_db = db;
}
public void Commit()
{
_db.SaveChanges();
}
public void Dispose()
{
_db.Dispose();
_db = null;
}
}
[Subject(typeof(BaseForm<>))]
public class When_disposing_form
{
static PosForm PosForm;
static ObjectContext ObjectContext;
private static IUnitOfWork UnitOfWork;
private static IPosDeviceRepository PosDeviceRepository;
Establish context = () =>
{
ObjectContext = new ErsManagementEntities();
UnitOfWork = A.Fake<UnitOfWork>(x => x.WithArgumentsForConstructor(
() => new UnitOfWork(ObjectContext)));
PosDeviceRepository = new PosDeviceRepository(ObjectContext);
};
Because of = () =>
{
using (PosForm = new PosForm(null, null, null, null, UnitOfWork, PosDeviceRepository, null, null, null))
{
}
};
It should_dispose_object_context = () => A.CallTo(() => UnitOfWork.Dispose()).MustHaveHappened();
}
@agross
Copy link

agross commented Jan 7, 2012

FakeItEasy und andere arbeiten mit dynamischen Proxies um das gefakte Objekt, hier der ObjectContext. Der Proxy erzeugt für alle virtuellen Methoden eine neue überschriebene Methode die als Interceptor die tatsächlichen Aufrufe am Fake mitschneidet. Sealed verbietet das Überschreiben von Methoden, daher die Fehlermeldung.

@Rookian
Copy link
Author

Rookian commented Jan 7, 2012

Aber nun fake ich doch nur das UnitOfWork?

@agross
Copy link

agross commented Jan 7, 2012

Die Erzeugung der UoW sieht seltsam aus. Da sie hier ja ein Fake sein kann brauchst du den ObjectContext hier gar nicht reinzureichen.

Dass die UoW beim Dispose den ObjectContext freigibt wäre ein Unit Test für die UoW. Das Problem bleibt dort aber auch bestehen.

@agross
Copy link

agross commented Jan 7, 2012

Du hast oben nicht die UoW gefaked. Du erzeugt einen Proxy um die Klasse UnitOfWork, nicht _I_UnitOfWork

@agross
Copy link

agross commented Jan 7, 2012

FYI, das Pattern das du oben anwendest nutzt man eher für abstrakte Klassen. Sprich, um ad hoc eine Implementierung davon zu erstellen mit teils ausimplementierten Methoden.

@agross
Copy link

agross commented Jan 7, 2012

... Ich seh gerade, das könntest du für die BaseForm einsetzen falls dort das Disposal der UoW implementiert ist.

Dann würde auch das Subject stimmen ;-)

@Rookian
Copy link
Author

Rookian commented Jan 7, 2012

Also mir gings im Test darum, dass wenn ein Formular geschlossen wird, der ObjectContext dispoded wird. Statt dem ObjectContext nehm ich nun das UoW.

FormBase Disposal:

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (components != null)
            {
                components.Dispose();
            }
            _unitOfWork.Dispose();
        }
        base.Dispose(disposing);
    }

Test:

[Subject(typeof(BaseForm<>))]
public class When_disposing_form
{
    static IUnitOfWork UnitOfWork;

    Establish context = () =>
    {
        UnitOfWork = A.Fake<IUnitOfWork>();
    };

    Because of = () =>
    {
        using (new PosForm(null, null, null, null, UnitOfWork, null, null, null, null))
        {

        }
    };

    It should_dispose_object_context = () => A.CallTo(() => UnitOfWork.Dispose()).MustHaveHappened();
}

@agross
Copy link

agross commented Jan 7, 2012 via email

@Rookian
Copy link
Author

Rookian commented Jan 7, 2012

Für WithArgumentsForConstructor muss ich doch eine konkrete Instanz angeben, was ich ja eigentlich nicht will ... und selbst wenn ich eine konkrete Instanz nutze funktioniert es nicht, da kein passender Ctor gefunden wird.

var baseForm = A.Fake<BaseForm>(x => x.WithArgumentsForConstructor(() => new PosForm(null, null, null, null, UnitOfWork, null, null, null, null)));

No constructor matches the passed arguments for constructor.

@agross
Copy link

agross commented Jan 7, 2012

This is what I was talking about:

using System;

using FakeItEasy;

using Machine.Specifications;

namespace ClassLibrary2
{
    public abstract class BaseForm<T> : IDisposable
    {
      readonly IUnitOfWork _uow;

      public BaseForm(IUnitOfWork uow)
      {
        _uow = uow;
      }

      public void Dispose()
      {
        _uow.Dispose();
      }
    }

  public interface IUnitOfWork : IDisposable
  {
  }

  [Subject(typeof(BaseForm<>))]
  public class When_closing_a_form
  {
    Establish context = () =>
    {
      UnitOfWork = A.Fake<IUnitOfWork>();
      Form = A.Fake<BaseForm<int>>(x =>x.WithArgumentsForConstructor(new[] {UnitOfWork}));
    };

    Because of = () => Form.Dispose();

    It should_dispose_the_unit_of_work =
      () => A.CallTo(() => UnitOfWork.Dispose()).MustHaveHappened();

    static BaseForm<int> Form;
    static IUnitOfWork UnitOfWork;
  }
}

@BjRo
Copy link

BjRo commented Jan 7, 2012

Ich bin leider etwas spät dazu gekommen, aber Alex hat ja alles passend beantwortet. Im ersten Listing ist in der Tat A.Fake das Problem. Da fehlt entweder ein I (IUnitOfWork) oder Deine UnitOfWork muss eine abstrakte Klasse mit virtuellen Methoden sein.

Kleine Anmerkung noch zu Deiner PosForm - Klasse. Da scheint mir was im Argen zu liegen. So viele Abhängigkeiten sind selten ein gutes Zeichen. Auch fragwürdig ist, dass Du eine PosForm erstellen kannst ohne den Grossteil der Abhängigkeiten zu befriedigen (null). Ich gehe mal nicht davon aus, dass der Rest der Abhängigkeiten optional ist. So etwas in der Art sagt aber der Konstruktor in Deinem Test aus.....

-Björn

@Rookian
Copy link
Author

Rookian commented Jan 8, 2012

@alex ich werds mal ausprobieren :)
@BJörn Ja die PosForm hat viele Abhängigkeiten, das is einer der nächsten Schritte, die ich angehen werde. Das ganze war vorher alles statisch mitten in der Form aufgerufen wurden.
Danke euch beiden!

@Rookian
Copy link
Author

Rookian commented Jan 8, 2012

Aus einem mir nicht erklärlichen Grund findet FakeItEasy meinen Ctor nicht. Ich habe noch einen 2. Ctor Param.

public abstract partial class BaseForm<TEntity> : Form where TEntity: class, IEntity
{
    private readonly IRepository<TEntity> _repository;
    private readonly IUnitOfWork _unitOfWork;

    protected BaseForm() { }

    protected BaseForm(IUnitOfWork unitOfWork, IRepository<TEntity> repository)
    {
        InitializeComponent();
        if (!DesignMode)
        {
            _unitOfWork = unitOfWork;
            _repository = repository;
           ...
        }
    } 
}

[Subject(typeof(BaseForm<>))]
public class When_disposing_a_form
{
    static IUnitOfWork UnitOfWork;
    static IRepository<PosDevice> Repository;
    static BaseForm<PosDevice> Form;

    Establish context = () =>
    {
        UnitOfWork = A.Fake<IUnitOfWork>();
        Repository = A.Fake<IRepository<PosDevice>>();
        Form = A.Fake<BaseForm<PosDevice>>(
            x => x.WithArgumentsForConstructor(new object[] { UnitOfWork, Repository }));
    };

    Because of = () =>
    {
        using (Form) { }
    };

    It should_dispose_object_context = () => A.CallTo(() => Form.Dispose()).MustHaveHappened();
}

@BjRo
Copy link

BjRo commented Jan 8, 2012

Ändere mal den Access-Modifier des 2 Konstruktors auf public

@Rookian
Copy link
Author

Rookian commented Jan 8, 2012

Hab ich gemacht. Selbes Problem.

@agross
Copy link

agross commented Jan 8, 2012

Works for me. Mir ist nicht klar, warum du dich an eine konkrete Entity à la PosDevice bindest, obwohl du

  • die Basisform testest,
  • ein Interface für deine Entities definiert hast.

Stell dir vor, irgendwann fliegt PosDevice raus, plötzlich bricht ein Test der nichts mit PosDevices zu tun hat.

using System;

using FakeItEasy;

using Machine.Specifications;

namespace ClassLibrary2
{
    public abstract class BaseForm<T> : IDisposable where T : class, IEntity
    {
        readonly IUnitOfWork _uow;

        protected BaseForm(IUnitOfWork uow)
        {
            _uow = uow;
        }

        public BaseForm(IUnitOfWork uow, IRepository<T> repository)
        {
            _uow = uow;
        }

        public void Dispose()
        {
            _uow.Dispose();
        }
    }

    public interface IEntity
    {
    }

    public interface IRepository<T>
    {
    }

    public interface IUnitOfWork : IDisposable
    {
    }

    [Subject(typeof(BaseForm<>))]
    public class When_closing_a_form
    {
        Establish context = () =>
        {
            UnitOfWork = A.Fake<IUnitOfWork>();
            var repository = A.Fake<IRepository<IEntity>>();

            Form = A.Fake<BaseForm<IEntity>>(x => x.WithArgumentsForConstructor(new object[] { UnitOfWork, repository }));
        };

        Because of = () => Form.Dispose();

        It should_dispose_the_unit_of_work =
          () => A.CallTo(() => UnitOfWork.Dispose()).MustHaveHappened();

        static BaseForm<IEntity> Form;
        static IUnitOfWork UnitOfWork;
    }
}

@Rookian
Copy link
Author

Rookian commented Jan 8, 2012

Hast vollkommen recht Alex. Aber die generische Basisklasse erbt von Form und da Form selbst schon von IDisposable erbt, macht es keinen Sinn von IDisposable zu erben.

Das Problem liegt an der Form Klasse. Wobei mir nicht klar ist, was das konkrete Problem wirklich ist.

@agross
Copy link

agross commented Jan 8, 2012

Okay, die Ableitung von Form hatte ich tatsächlich übersehen.

@Rookian
Copy link
Author

Rookian commented Jan 8, 2012

Ja an der liegt es. Für mich scheint es so als ob FakeItEasy da nen Bug hat.

@agross
Copy link

agross commented Jan 8, 2012

Das ist eher eine Usability Issue (kannst sie trotzdem gerne reporten). Ich habe FIE selbst kompiliert, das ist die Exception die der Dynamic Proxy Generator von Castle wirft:

Due to limitations in CLR, DynamicProxy was unable to successfully replicate non-inheritable attribute System.Security.Permissions.UIPermissionAttribute on ClassLibrary2.BaseForm`1[[ClassLibrary2.IEntity, ClassLibrary2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].ProcessMnemonic. To avoid this error you can chose not to replicate this attribute type by calling 'Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add(typeof(System.Security.Permissions.UIPermissionAttribute))'.

Sie wird dann fälschlicherweise in die Exception die du bekommst "umgewandelt" und damit maskiert. Zugriff auf die korrekte InnerException wäre hier praktisch.

Befolgt man den Hinweis aus dem Exception Text geht's übrigens gleich weiter mit TargetInvocationException die eine NullReferenceException beinhaltet. Grund hierfür ist dass der ctor von Form auf irgendwelche Properties zugreift die nicht initialisiert sind (konkret: DefaultMargin):

   at Castle.Proxies.BaseForm`1Proxy.get_DefaultMargin()
   at System.Windows.Forms.Control..ctor(Boolean autoInstallSyncContext)
   at System.Windows.Forms.Control..ctor()
   at System.Windows.Forms.ScrollableControl..ctor()
   at System.Windows.Forms.ContainerControl..ctor()
   at System.Windows.Forms.Form..ctor()
   at ClassLibrary2.BaseForm`1..ctor(IUnitOfWork uow, IRepository`1 repository) in D:\Users\agross\Coding\.NET\ClassLibrary2\ClassLibrary2\Class1.cs:line 19
   at Castle.Proxies.BaseForm`1Proxy..ctor(IInterceptor[] , IUnitOfWork , IRepository`1 )

Merke: Windows Forms zu faken is nich.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment