I've written an interface to wrap the Windows Threadpool API and many of those functions return plain Pointer
types.
Now I'm writing tests and want to use the delphi-mocks framework to mock that wrapper interface.
The problem is that the TMock
setup interface takes a TValue
object to specify a default return value for the mocked functions and I can't see a way how to do that correctly from the available TValue
functions. Though I've seen that ekPointer
is a valid TTypeKind
value.
When the mocked function is called I receive an EInvalidCast
exception from the corresponding RTTI invocation.
That happens specifically, when the RTTI invocation tries to cast the return type value from the implicit TValue
object.
My code looks roughly like this1:
type
PTP_POOL = Pointer;
PTP_CLEANUP_GROUP = Pointer;
IThreadPoolApi = interface(IInterface)
{...}
function CreateThreadPool() : PTP_POOL;
function CreateThreadpoolCleanupGroup() : PTP_CLEANUP_GROUP;
{...}
end;
the class under test
type
TThreadPool = class(TInterfacedObject, IThreadPool)
FTPApi : IThreadPoolApi;
FPTPPool : PTP_POOL;
FPTPCleanupGroup : PTP_CLEANUP_GROUP;
{...}
public
constructor Create(iapi : IThreadPoolApi);
end;
implementation
constructor TThreadPool.Create(iapi : IThreadPoolApi);
begin
inherited Create();
FTPApi := iapi;
{**** Here I get a EInvalidCast exception when mocking the interface ****}
FPTPPool := FTPApi.CreateThreadPool();
if(not assigned(FPTPPool)) then
begin
{Raise exception}
raise EThreadPoolError('Cannot create TP thread pool');
end;
{**** This case should be tested ****}
FPTPCleanupGroup := FTPApi.CreateThreadpoolCleanupGroup();
if(not assigned(FPTPPool)) then
begin
{Raise exception}
raise EThreadPoolError('Cannot create TP cleanup group');
end;
{...}
end;
and the testing stuff
procedure ThreadPoolTest.TestFail_CreateThreadpoolCleanupGroup();
var
apiMock : TMock<IThreadPoolApi>;
testproc : TTestLocalMethod;
begin
apiMock := TMock<IThreadPoolApi>Create();
{**** Needed to reach the call of CreateThreadpoolCleanupGroup
but EInvalidCast is raised ****}
apiMock.Setup.WillReturnDefault('CreateThreadPool',PTP_POOL($FFFFFFFF));
{**** The case to be tested ****}
apiMock.Setup.WillExecute('CreateThreadpoolCleanupGroup',
function (const args : TArray<TValue>; const ReturnType : TRttiType)
: TValue
begin
result := nil;
end);
testproc :=
procedure()
var
threadpool : IThreadPool;
begin
threadpool := TThreadPool.Create(apiMock);
end;
DUnitX.Assert.WillRaise(testproc,EThreadPoolError,
'Cannot create TP cleanup group');
end;
TL;DR;
So the question is:
What do I need to do that TValue
is created properly to contain a PTP_POOL
pointer type?
1)It's a bit too much code to setup a MCVE, so I just sketch it here to give you the background, see the {**** highlighted comments ****}
When assigning a raw pointer to a TValue
, use the TValue.From<T>()
method, eg:
TValue.From<PTP_POOL>(nil);
Or, if you want to return a literal value:
TValue.From<PTP_POOL>(PTP_POOL(value));
TValue
does not have an implicit conversion for raw pointers, but it does have implicit conversions for TObject
and TClass
pointers.
If you assign an untyped nil pointer directly to TValue
, it calls TValue.Implicit(TObject)
, which will return the TValue.Empty
property when passed a nil pointer.
If you assign a typed non-TObject
pointer directly to TValue
, it calls TValue.Implicit(TClass)
, which returns TValue.Empty
if the pointer is nil, otherwise it returns a TValue
of type TClass
with the value of the pointer, even if it is not actually pointing at a valid class type.
If you pass a pointer to TValue.From<T>()
, it returns a TValue
of type T
with the value of the pointer, even if it is nil.
This difference is subtle, but very important.