Search code examples
delphimockingspring4d

Delphi mocking with nullable types


What is the best way to setup Delphi DSharp mocks with Nullable types from the Spring framework? I've tried various approaches and I know I'm probably missing something really rudimentary, but I just can't figure out how to get the following code to work:

program DSharkMockNullable;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  DSharp.Testing.Mock,
  Spring,
  System.SysUtils;

type

  {$M+}
  IBaseMock = interface
  ['{B3311345-3C1F-47E3-8235-57B1BA04E957}']
    function GetId:  TNullableInteger;
    procedure SetId(Value: TNullableInteger);
    property Id: TNullableInteger read GetId write SetId;
  end;

  {$M+}
  IMockMe = interface(IBaseMock)
  ['{07F8F233-E8F5-4743-88C5-97A66BB01E29}']
    function GetObjectId:  TNullableInteger;
    procedure SetObjectId(Value: TNullableInteger);
    property ObjectId: TNullableInteger read GetObjectId write SetObjectId;
    function GetObjectName:  TNullableString;
    procedure SetObjectName(Value: TNullableString);
    property ObjectName: TNullableString read GetObjectName write SetObjectName;
  end;

  TMyObject = class
  public
    function ObjectIdAsString(const AObject: IMockMe): string;
  end;


{ TMyObject }

function TMyObject.ObjectIdAsString(const AObject: IMockMe): string;
var
  LId: Integer;
  LObjectId: Integer;
  LObjectName: string;
begin
  LId := AObject.Id;  // ***** FAILS HERE with an "Invalid Typecast" error
  LObjectId := AObject.ObjectId;
  LObjectName := AObject.ObjectName;
  Result := Format('Id: %d', [LId]);
  Result := Format('Object Id: %d', [LObjectId]);
  Result := Format('Object Name: %s', [LObjectName]);
end;

var
  LMock: Mock<IMockMe>;
  LObject: TMyObject;
begin
  try
    LMock.Setup.WillReturn(123).Any.WhenCalling.Id;
    LMock.Setup.WillReturn(456).Any.WhenCalling.ObjectId;
    LMock.Setup.WillReturn('blahblah').Any.WhenCalling.ObjectName;

    LObject := TMyObject.Create;
    Writeln(LObject.ObjectIdAsString(LMock.Instance));
    LObject.Free;

    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Any ideas or suggestions would be great? I'm using Delphi XE5. Thanks. Rick.


Solution

  • The correct code should look like this (not using type inference):

    LMock.Setup.WillReturn<TNullableInteger>(123).Any.WhenCalling.Id;
    LMock.Setup.WillReturn<TNullableInteger>(456).Any.WhenCalling.ObjectId;
    LMock.Setup.WillReturn<TNullableString>('blahblah').Any.WhenCalling.ObjectName;
    

    As addition to the correct answer that Rick already gave here some explanation why that is the case:

    The original code uses type inference for the values passed to WillReturn and it infers them as ShortInt, SmallInt (compiler uses the smallest signed ordinal type that fits) and string. These are stored as TValue and returned when the method is being called. But then the RTTI tries to cast the TValue to the return type and the method which fails because it does not know how to cast a ShortInt or SmallInt to a Nullable<Integer> or a string to a Nullable<string> since TValue.TryCast does not know about any Implicit operator overloads of the Nullable<> type and there is no way to register any custom converters.