Gary Shelton
Gary Shelton

Reputation: 143

Test in DUnitx with Delphi-Mocks passing private record

I am new to DUnitx and Delphi-Mocks so please be patient. The only other post I could find on this topic was 3 years old and not answered. Returning records in Delphi-Mocks

Delphi Rio 10.3. Windows 10

I want to test this procedure:

procedure TdmMariaDBConnection.Notify;
var
  LViewModel : IPsViewModel;
begin
  FMainViewModel.HandleCommands(FCommandRecord);
  for LViewModel in FObservers do
    LViewModel.HandleCommands(FCommandRecord);
end;

The interfaces and record type are declared as:

    IPsView = interface(IInvokable)
        ['{F5532762-09F8-42C4-9F9F-A8F7FF7FA0C6}']
        procedure HandleCommands(const Value: TPsCommandRecord);
        procedure AfterCreate;
        procedure BeforeDestroy;
      end;

      IPsViewModel = interface(IInvokable)
        ['{322DAB08-6A7C-4B61-B656-BC5346ACFC14}']
        procedure HandleCommands(const Value: TPsCommandRecord);
      end;

      IPsMainViewModel = interface(IInvokable)
        ['{98FFB416-6C22-492F-BC85-D9A1ECA667FE}']
        procedure Attach(const observer: IPsView);
        procedure Notify;
        procedure LoadFrame(const Value: TPanel);
        procedure LoadForm(const Value: integer);
        procedure LoadModalForm(const Value: integer);
        procedure HandleCommands(const Value: TPsCommandRecord);
        procedure SetViewFactory(Value: IPsViewFactory);
        property ViewFactory: IPsViewFactory write SetViewFactory;
      end;

  TPsCommandRecord = record
    CommandType: integer;
    CommandObject: TObject;
    CommandMessage: TPsTaskDialogMessageRecord;
  end;

I have the Notify procedure in the protected section

type
  TdmMariaDBConnection = class(TDataModule, IPsModel)
    procedure DataModuleDestroy(Sender: TObject);
    procedure DataModuleCreate(Sender: TObject);
  private
    FObservers : TList<IPsViewModel>;
    FMainViewModel : IPsMainViewModel;
    FCommandRecord : TPsCommandRecord;
  protected
    procedure Notify;
  ….
end;

In my test project I have a descendent class

 TTestabledmMariaDBConnection = class(TdmMariaDBConnection)
  end;
var
  CUT : TTestabledmMariaDBConnection;

procedure TTestModel_MariaDBConnection.Setup;
begin
  CUT := TTestabledmMariaDBConnection.Create(nil);
end;

so I can call protected methods. What I have so far that doesn't work because I cannot provide the private record instance from TdmMariaDBConnection, and just focusing on the MainViewModel for now.

procedure TTestModel_MariaDBConnection.NotifyCallsMainViewModelHandleCommands;
var
  MVMMock : TMock<IPsMainViewModel>;
  LCommandRecord : TPsCommandRecord;
begin
  //Arrange
  MVMMock := TMock<IPsMainViewModel>.Create;
  MVMMock.Setup.Expect.Once.When.HandleCommands(LCommandRecord);
  //Act
   CUT.Attach(MVMMock);
   CUT.Notify;
  //Assert
  try
    MVMMock.Verify();
    Assert.Pass();
  except on E: EMockException do
    Assert.Fail(E.Message);
  end;
end;

Obviously the addition of LCommandRecord are wrong I just added them to get it to compile. I need(I think) the record instance from The test class in the setup. I tried adding a function to get that but it didn't work either.

function TdmMariaDBConnection.GetCommandRecord: TPsCommandRecord;
begin
  Result := FCommandRecord;
end;

MVMMock.Setup.Expect.Once.When.HandleCommands(CUT.GetCommandRecord);

The test doesn't even complete, I get an incomplete circle in TestInsight GUI instead of the hoped for Green check.

Any help would be appreciated. Also is this the right use of Verify? I can only find the explanation that it does nothing when passing, so how to add an Assert?

Thanks in advance Gary

Upvotes: 2

Views: 322

Answers (1)

Stefan Glienke
Stefan Glienke

Reputation: 21713

The way you setup the mock it will be very strict about the parameters being passed and checks for equality to the specified setup when calling Verify.

There is also a long standing issue in Delphi Mocks that record parameters are not properly compared for equality (they only equal if the parameters where the exact same address - see SameValue in Delphi.Mocks.Helpers.pas - I know of this issue because it is my code being used with my permission - I wrote a better version some while ago being used in Spring4D which also has mocking fwiw). This is why even if it would not run in a circle with your added GetCommandRecord it might not pass.

What I usually suggest people to do (I wrote 2 mocking libraries for Delphi so far) when using mocks is to be as permissive as possible. Fortunately Delphi Mocks supports parameter matcher that let you specify that actually you don't care that much for the exact value of the parameter being passed.

That being said simply change your setup to call

MVMMock.Setup.Expect.Once.When.HandleCommands(It0.IsAny<TPsCommandRecord>);

That tells the internal matcher recording calls to the mock from the SUT that it does not matter what value comes in which satisfies the expectation.

By the way for a similar reason as with the SameValue bug it will not work using It0.IsEqualTo(LCommandRecord) because the used comparer for records internally calls System.Generics.Defaults.Equals_Binary which just does a flat memory compare of the record which possibly fails for any reference type.

Upvotes: 1

Related Questions