簡體   English   中英

如何使用DUnit模擬Spring4D事件

[英]How to Mock Spring4D Events with DUnit

我正在努力用DUnit成功模擬Spring4d事件。

實際上,我更喜歡模擬一個返回事件模擬的模擬...

這是基本結構。

TMyObject --EventContainer--> TMock<IEventContainer> --Event--> TMock<IEvent>

TMyObject具有屬性EventContainer:IEventContainer

IEventContainer具有屬性Event:IMyEvent

我想嘲笑

MyObject.EventContainer.Event.Add

我測試了我能想到的每種可能性。 我得到了AV或無效的演員表。 我把源代碼放在下面。 如果有人可以幫助我完成這項工作,那真是太好了!

program Project2;

{$APPTYPE CONSOLE}
{$R *.res}

uses
    System.SysUtils,
    DUnitTestRunner,
    Spring.Events,
    Spring,
    Classes,
    TestFramework,
    Delphi.Mocks;
    //Unit1 in 'Unit1.pas';

type

{$M+}
    IMyEvent = interface(IEvent<TNotifyEvent>)
        procedure Add(const handler: TMethod);
    end;
{$M-}
{$M+}

    IMyEventMock = interface(IMyEvent)
        procedure Add(const handler: TMethod);
    end;
{$M-}
{$M+}

    IEventContainer = interface(IInterface)
        function GetEvent: IMyEvent;
        procedure SetEvent(const Value: IMyEvent);
        property Event: IMyEvent
            read GetEvent
            write SetEvent;
    end;
{$M-}
{$M+}

    ITestEventContainer = interface(IEventContainer)
        function GetEvent: TMock<IMyEvent>;
        procedure SetEvent(const Value: TMock<IMyEvent>);
        property Event: TMock<IMyEvent>
            read GetEvent
            write SetEvent;
    end;
{$M-}
{$REGION 'TEventContainer'}

    TEventContainer = class(TInterfacedObject, IEventContainer)

    private
        FAEvent: IMyEvent;
        function GetEvent: IMyEvent;
        procedure SetEvent(const Value: IMyEvent);

    public
        property Event: IMyEvent
            read GetEvent
            write SetEvent;
    end;

{$ENDREGION}
{$REGION 'TMyObject'}

    TMyObject = class(TObject)
    private
        FEventContainer: IEventContainer;
        function GetEventContainer: IEventContainer;
        procedure SetEventContainer(const Value: IEventContainer);

    public
        property EventContainer: IEventContainer
            read GetEventContainer
            write SetEventContainer;
    end;

{$ENDREGION}
{$REGION 'TMyObjectTest'}

    TMyObjectTest = class(TTestCase)
    strict private
        FMyObject: TMyObject;
        FMyEventContainerMock: TMock<IEventContainer>;
        FMyTestEventContainerMock: TMock<ITestEventContainer>;
        FEventMock: TMock<IMyEventMock>;

    public
        procedure SetUp; override;
        procedure TearDown; override;

    published
        procedure Test_InstanceAsValue;
        procedure Test_Value_Make;
        procedure Test_Value_From;
        procedure Test_Value_From_Instance;
        procedure Test_Value_From_Variant;
        procedure Test_Value_From_Variant_Instance;
        procedure Test_Mocked_Container_Value_Make;
        procedure Test_Mocked_Container_Value_From;
        procedure Test_Mocked_Container_Value_From_Instance;
        procedure Test_Mocked_Container_Value_From_Variant;
        procedure Test_Mocked_Container_Value_From_Variant_Instance;
    end;
{$ENDREGION}


{$REGION 'TEventContainer'}

function TEventContainer.GetEvent: IMyEvent;
begin
    Result := FAEvent;
end;

procedure TEventContainer.SetEvent(const Value: IMyEvent);
begin
    FAEvent := Value;
end;
{$ENDREGION}

{$REGION 'TMyObject'}

function TMyObject.GetEventContainer: IEventContainer;
begin
    Result := FEventContainer;
end;

procedure TMyObject.SetEventContainer(const Value: IEventContainer);
begin
    FEventContainer := Value;
end;
{$ENDREGION}

{$REGION 'TMyObjectTest'}

procedure TMyObjectTest.SetUp;
begin
    inherited;

    FMyObject := TMyObject.Create;

    FMyEventContainerMock := TMock<IEventContainer>.Create;

    FMyObject.EventContainer := FMyEventContainerMock;

end;

procedure TMyObjectTest.TearDown;
begin
    inherited;

    FMyObject.Free;

    FMyObject := nil;
end;

procedure TMyObjectTest.Test_Value_Make;
var aValue : TValue;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    TValue.Make(@FEventMock, TypeInfo(IMyEventMock), aValue);

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', aValue);

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_InstanceAsValue;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', FEventMock.InstanceAsValue);

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Mocked_Container_Value_From;
begin

    FMyTestEventContainerMock := TMock<ITestEventContainer>.Create;

    FMyObject.EventContainer := FMyTestEventContainerMock;

    FEventMock := TMock<IMyEventMock>.Create;

    FMyTestEventContainerMock.SetUp.WillReturnDefault('GetEvent', FEventMock.InstanceAsValue);

    FMyObject.EventContainer.Event;

end;

procedure TMyObjectTest.Test_Mocked_Container_Value_From_Instance;
begin
FMyTestEventContainerMock := TMock<ITestEventContainer>.Create;

    FMyObject.EventContainer := FMyTestEventContainerMock;

    FEventMock := TMock<IMyEventMock>.Create;

    FMyTestEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.From(FEventMock));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Mocked_Container_Value_From_Variant;
begin
    FMyTestEventContainerMock := TMock<ITestEventContainer>.Create;

    FMyObject.EventContainer := FMyTestEventContainerMock;

    FEventMock := TMock<IMyEventMock>.Create;

    FMyTestEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.FromVariant(FEventMock));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Mocked_Container_Value_From_Variant_Instance;
begin
    FMyTestEventContainerMock := TMock<ITestEventContainer>.Create;

    FMyObject.EventContainer := FMyTestEventContainerMock;

    FEventMock := TMock<IMyEventMock>.Create;

    FMyTestEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.FromVariant(FEventMock.Instance));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Mocked_Container_Value_Make;
var aValue : TValue;
begin
    FMyTestEventContainerMock := TMock<ITestEventContainer>.Create;

    FMyObject.EventContainer := FMyTestEventContainerMock;

    FEventMock := TMock<IMyEventMock>.Create;

    TValue.Make(@aValue, TypeInfo(TMock<IMyEventMock>), aValue);

    FMyTestEventContainerMock.SetUp.WillReturnDefault('GetEvent', aValue);

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Value_From;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.From(FEventMock));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Value_From_Instance;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.From(FEventMock.Instance));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Value_From_Variant;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.FromVariant(FEventMock));

    FMyObject.EventContainer.Event;
end;

procedure TMyObjectTest.Test_Value_From_Variant_Instance;
begin
    FEventMock := TMock<IMyEventMock>.Create;

    FMyEventContainerMock.SetUp.WillReturnDefault('GetEvent', TValue.FromVariant(FEventMock.Instance));

    FMyObject.EventContainer.Event;
end;

begin
    RegisterTest(TMyObjectTest.Suite);
    try
        DUnitTestRunner.RunRegisteredTests;
        ReadLn;
    except
        on E: Exception do
        begin
            Writeln(E.ClassName, ': ', E.Message);
            ReadLn;
        end;
    end;

end.

首先,您的方法是錯誤的。 繼承接口,然后添加{$M+}只會包含從那里添加的方法的方法信息。 這意味着即使您添加一個與父接口具有相同簽名的方法也不會使模擬工作,因為代碼仍將調用父接口方法,而不是您添加的方法。

此外,在這種情況下,DelphiMocks是接口將TValue轉換為其父類型的錯誤的受害者。 只是不支持此功能-請參見Rtti.ConvIntf2Intf

我建議使用繼承自IInvokable的IEvent編譯Spring4D,以獲取方法信息,並避免從中繼承。

如果這樣做,則以下測試將通過-所有其他測試都僅通過了模擬錯誤:

Test_InstanceAsValue;
Test_Value_From_Instance;
Test_Mocked_Container_Value_From;

在Spring4D 1.2中,我們引入了一個新的攔截庫,該庫也用於我們的模擬解決方案。 此外,容器將能夠提供自動模擬功能。 因此,您可以這樣編寫測試:

var
  container: TContainer;
  event: IMyEvent;
begin
  container := TContainer.Create;
  container.AddExtension<TAutoMockExtension>;
  try
    FMyObject.EventContainer := container.Resolve<ITestEventContainer>;

    event := FMyObject.EventContainer.Event;
    event.Add(nil);
  finally
    container.Free;
  end;
end;

容器將為需要解析的任何類型創建模擬,而這些類型都不知道。 在此測試中,您可以注冊要測試的類,並且容器會自動將所有依賴項作為模擬注入。

var
  container: TContainer;
  event: IMyEvent;
begin
  container := TContainer.Create;
  container.AddExtension<TAutoMockExtension>;
  container.RegisterType<TMyObject>.InjectProperty('EventContainer');
  container.Build;
  try
    FMyObject := container.Resolve<TMyObject>;

    event := FMyObject.EventContainer.Event;
    event.Add(nil);
  finally
    container.Free;
  end;
end;

您甚至可以進一步將自動模擬容器集成到基本的測試用例類中:

program Project2;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils,
  DUnitTestRunner,
  TestFramework,
  Spring.Events,
  Spring,
  Spring.Container,
  Spring.Container.Registration,
  Spring.Container.AutoMockExtension,
  Spring.Mocking;

type
  IMyEvent = IEvent<TNotifyEvent>;

  IEventContainer = interface(IInvokable)
    function GetEvent: IMyEvent;
    procedure SetEvent(const Value: IMyEvent);
    property Event: IMyEvent read GetEvent write SetEvent;
  end;

  TMyObject = class(TObject)
  private
    FEventContainer: IEventContainer;
  public
    property EventContainer: IEventContainer read FEventContainer write FEventContainer;
  end;

  TAutoMockingTestCase<T: class> = class(TTestCase)
  protected
    fContainer: TContainer;
    fSUT: T;
    procedure SetUp; overload; override;
    procedure TearDown; override;
    procedure SetUp(const registration: TRegistration<T>); reintroduce; overload; virtual;
  end;

  TMyTest = class(TAutoMockingTestCase<TMyObject>)
  protected
    procedure SetUp(const registration: TRegistration<TMyObject>); override;
  published
    procedure Test_EventAdd;
  end;

procedure TAutoMockingTestCase<T>.SetUp(const registration: TRegistration<T>);
begin
end;

procedure TAutoMockingTestCase<T>.SetUp;
begin
  inherited;
  fContainer := TContainer.Create;
  fContainer.AddExtension<TAutoMockExtension>;
  SetUp(fContainer.RegisterType<T>);
  fContainer.Build;
  fSUT := fContainer.Resolve<T>;
end;

procedure TAutoMockingTestCase<T>.TearDown;
begin
  fSUT.Free;
  fContainer.Free;
  inherited;
end;

procedure TMyTest.SetUp(const registration: TRegistration<TMyObject>);
begin
  registration.InjectProperty('EventContainer');
end;

procedure TMyTest.Test_EventAdd;
begin
  fSUT.EventContainer.Event.Add(nil);
end;

begin
  RegisterTest(TMyTest.Suite);
  try
    DUnitTestRunner.RunRegisteredTests;
    ReadLn;
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      ReadLn;
    end;
  end;
end.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM