How to pass/return records with PascalMock requiring Variants?

68 Views Asked by At

PascalMock with Delphi 2010 works fine when using simple data types or objects. With records, however, there's trouble.

TMock.Returns takes an array of TVarRec, so I can't just pass a one in.

There is a sample which casts it to a pointer, but since it represents a value, that doesn't make sense to me. When that record is going to get passed between some functions, in the end I'll get back a copy with a different address.

Another thing I tried was to copy the record at the byte level into a Variant using VarArrayCreate. This mysteriously results into an error casting to integer when fed to PascalMock. I get the same error when I just pass in Variant(TBytes.Create(1, 2, 3)). Without the explicit case to Variant, again I get an integer (pointer?).

Of course I could always write a mock completely by hand, but given the age of PascalMock I expected this to be a solved issue. Note that I'm not even using the latest AutoMockIntf addition. Is there a proper way of converting any record to/from a Variant that PascalMocks can work with? Or is there perhaps a better way to work with records in this context?

Here's some sample code of what I'm trying to achieve:

interface

uses
  SysUtils,
  TestFramework,
  PascalMock;

type
  TMyRecord = record
    // Sample fields but should work for any record structure
    FirstField: Integer;
    SecondField: string;
    class operator Equal(const A, B: TMyRecord): Boolean;
    class operator NotEqual(const A, B: TMyRecord): Boolean;
  end;

  IRecordMaker = interface
  ['{436D6FC3-3DCD-4EFF-B206-1C3E1A5561D1}']
    function Make: TMyRecord;
  end;

  TFakeRecordMaker = class(TMock, IRecordMaker)
  public
    function Make: TMyRecord;
  end;

  IRecordSaver = interface
  ['{91BB1347-A34E-4A77-8993-3ADBFBE8726D}']
    procedure Save(const ARecord: TMyRecord);
  end;

  TFakeRecordSaver = class(TMock, IRecordSaver)
  public
    procedure Save(const ARecord: TMyRecord);
  end;

  TRunner = class
  private
    FRecordMaker: IRecordMaker;
    FRecordSaver: IRecordSaver;
  public
    constructor Create(ARecordMaker: IRecordMaker; ARecordSaver: IRecordSaver);
    procedure Run;
  end;

  TestRunner = class(TTestCase)
  private
    FRecordMaker: TFakeRecordMaker;
    FRecordSaver: TFakeRecordSaver;
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure MakesRecordAndSavesIt;
  end;

implementation

{ TMyRecord }

class operator TMyRecord.Equal(const A, B: TMyRecord): Boolean;
begin
  Result := (A.FirstField = B.FirstField) and (A.SecondField = B.SecondField);
end;

class operator TMyRecord.NotEqual(const A, B: TMyRecord): Boolean;
begin
  Result := not (A = B);
end;

{ TFakeRecordMaker }

function TFakeRecordMaker.Make: TMyRecord;
begin
  // Remember having been called and return the desired data
  Result := AddCall('Make').ReturnValue;
end;

{ TFakeRecordSaver }

procedure TFakeRecordSaver.Save(const ARecord: TMyRecord);
begin
  // Remember having been called and with what arguments
  AddCall('Save').WithParams([ARecord]);
end;

{ TRunner }

constructor TRunner.Create(ARecordMaker: IRecordMaker; ARecordSaver: IRecordSaver);
begin
  inherited Create;
  FRecordMaker := ARecordMaker;
  FRecordSaver := ARecordSaver;
end;

procedure TRunner.Run;
var
  LRecord: TMyRecord;
begin
  LRecord := FRecordMaker.Make;
  FRecordSaver.Save(LRecord);
end;

{ TestRunner }

procedure TestRunner.SetUp;
begin
  inherited;
  FRecordMaker := TFakeRecordMaker.Create;
  FRecordSaver := TFakeRecordSaver.Create;
end;

procedure TestRunner.MakesRecordAndSavesIt;
var
  LRecord: TMyRecord;
  LSUT: TRunner;
begin
  // The test data
  LRecord.FirstField := 3;
  LRecord.SecondField := 'bla';
  // The first collaborator serving as stub should return this data
  FRecordMaker.Expects('Make').Returns(LRecord);
  // The second collaborator serving as mock should be passed this data
  FRecordSaver.Expects('Save').WithParams([LRecord]);
  LSUT := TRunner.Create(FRecordMaker, FRecordSaver);
  try
    // Hopefully the SUT will get the data and pass it on
    LSUT.Run;
    // Check if the remembered calls match the expected ones
    FRecordSaver.Verify;
  finally
    LSUT.Free;
  end;
end;

procedure TestRunner.TearDown;
begin
  FRecordSaver.Free;
  FRecordMaker.Free;
  inherited;
end;

initialization
  RegisterTest(TestRunner.Suite);

end.
0

There are 0 best solutions below