How do I mock a method with an open array parameter in PascalMock?
I'm currently in the process of getting started with unit testing and mocking for good and I stumbled over the following method that I can't seem to fabricate a working mock implementation for:
function GetInstance(const AIID: TGUID;
out AInstance;
const AArgs: array of const;
const AContextID: TImplContextID = CID_DEFAULT): Boolean;
(TImplContextID
is just a type alias for Integer)
This is how far I got:
function TImplementationProviderMock.GetInstance(
const AIID: TGUID;
out AInstance;
const AArgs: array of const;
const AContextID: TImplContextID): Boolean;
var
lCall: TMockMethod;
begin
lCall := AddCall('GetInstance').WithParams([@AIID, AContextID]);
Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
Result := lCall.ReturnValue;
end;
But I haven't been able to figure out how I am supposed to mock the open array parameter AArgs
. Any ideas?
Also, is there maybe a simpler way to to return the out
-parameter AInstance
and is using the @
-notation for the TGUID
-typed parameter (essentially a record, i.e. a value type) the right way to go?
Is it possible to mock this method with the current version of PascalMock at all?
Update 2: I have now cut down the question text for clarity. Originally it contained the following erroneous implementation of the mock method which was what Mason's reply开发者_JAVA技巧 refers to:
function TImplementationProviderMock.GetInstance(
const AIID: TGUID;
out AInstance;
const AArgs: array of const;
const AContextID: TImplContextID): Boolean;
begin
Result := AddCall('GetInstance')
.WithParams([@AIID, AContextID])
.ReturnsOutParams([AInstance])
.ReturnValue;
end;
In this the compiler complained about the .ReturnsOutParams([AInstance])
saying "Bad argument type in variable type array constructor.".
It looks like ReturnsOutParams
expects an array of const, which is implemented internally as an array of TVarRec. TVarRec is a record that's sort of like a variant but different, and it requires a defined type for the compiler to fill it. An untyped parameter won't go into it.
This sort of thing could probably be done with Delphi 2010's extended RTTI, but not with TVarRec.
I've now come up with a somewhat elaborate solution which is not exactly ideal from an OO- POV because it requires that the implementer of the test knows how the mock is implemented internally but I think that is still somewhat acceptable as no assumption can be made about how else to specify an open array argument expectation anyway (at least none that would compile).
So, this is what the implementation of my mocked method now looks like:
function TImplementationProviderMock.GetInstance(
const AIID: TGUID;
out AInstance;
const AArgs: array of const;
const AContextID: TImplContextID): Boolean;
var
lCall: TMockMethod;
lArgs: TOpenArray;
begin
lArgs := ConcatArrays([ArgsToArray([@AIID]), ArgsToArray(AArgs), ArgsToArray([AContextID])]);
lCall := AddCall('GetInstance').WithParams(lArgs);
Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
Result := lCall.ReturnValue;
end;
As you can see the heart of my solution was to construct my own array of TVarRec
(aka TOpenArray
) which I could then pass to the WithParams
-method. I wrote a couple of utility routines that allowed me to merge the explicit arguments with the open array arguments into a single new array.
Here is the implementation of ConcatArrays
:
type TOpenArray = array of TVarRec;
function ConcatArrays(const AArrays: array of TOpenArray): TOpenArray;
var
lLength: Integer;
lArray: TOpenArray;
lIdx: Integer;
lElem: TVarRec;
begin
lLength := 0;
for lArray in AArrays do
Inc(lLength, Length(lArray));
SetLength(Result, lLength);
lIdx := -1;
for lArray in AArrays do
for lElem in lArray do
begin
Inc(lIdx);
Result[lIdx] := lElem;
end;
end;
I do have a strong suspicion that these routines could probably be massively optimized by someone with a deeper understanding of how Delphi handles dynamic and open arrays internally.
Anyway, with this solution in place at the test site I now have to ignore the fact that there even is an open array parameter in the mocked method. I simply specify the expectation as such:
FMock.Expects('GetInstance').WithParams([@IMyIntf, 1, 2, 3, lContextID]).ReturnsOutParam(lDummy).Returns(True);
... where the 1, 2, 3
-bit is really the expected open array argument.
精彩评论