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.