Stubbing complex return values

Just imagine that the ForgotMyPassword method as defined in previous articles does perform an internal test:

procedure TLoginController.ForgotMyPassword(const UserName: RawUTF8);
var U: TUser;
begin
  U := fUserRepository.GetUserByName(UserName);
  Assert(U.Name=UserName);
  U.Password := Int32ToUtf8(Random(MaxInt));
  if fSmsSender.Send('Your new password is '+U.Password,U.MobilePhoneNumber) then
    fUserRepository.Save(U);
end;

This will fail the test for sure, since by default, GetUserByName stubbed method will return a valid but void record. It means that U.Name will equal '', so the highlighted line will raise an EAssertionFailed exception.

Here is how we may enhance our stub, to ensure it will return a TUser value matching U.Name='toto':

var UserRepository: IUserRepository;
    U: TUser;
  (...)
  U.Name := 'toto';
  TInterfaceMock.Create(TypeInfo(IUserRepository),UserRepository,self).
    Returns('GetUserByName','"toto"',RecordSaveJSON(U,TypeInfo(TUser))).
    ExpectsCount('Save',qoEqualTo,1);

The only trick in the above code is that we use RecordSaveJSON() function to compute the internal JSON representation of the record, as expected by mORMot's data marshaling.

Stubbing via a custom delegate or callback

In some cases, it could be very handy to define a complex process for a given method, without the need of writing a whole implementation class.

A delegate or event callback can be specified to implement this process, with three parameters marshaling modes:
- Via some Named[] variant properties (which are the default for the Ctxt callback parameter) - the easiest and safest to work with;
- Via some Input[] and Output[] variant properties;
- Directly as a JSON array text (the fastest, since native to the mORMot core).

Let's emulate the following behavior:

function TServiceCalculator.Subtract(n1, n2: double): double;
begin
  result := n1-n2;
end;

Delegate with named variant parameters

You can stub a method using a the Named[] variant arrays as such:

  TInterfaceStub.Create(TypeInfo(ICalculator),ICalc).
    Executes('Subtract',IntSubtractVariant);
  (...)
  Check(ICalc.Substract(10.5,1.5)=9);

The callback function can be defined as such:

procedure TTestServiceOrientedArchitecture.IntSubtractVariant(
  Ctxt: TOnInterfaceStubExecuteParamsVariant);
begin
  Ctxt['result'] := Ctxt['n1']-Ctxt['n2'];
end;

That is, callback shall use Ctxt['..'] property to access the parameters and result as variant values.

In fact, we use the Ctxt.Named[] default property, so it is exactly as the following line:

  Ctxt.Named['result'] := Ctxt.Named['n1']-Ctxt.Named['n2'];

If the execution fails, it shall execute Ctxt.Error() method with an associated error message to notify the stubbing process of such a failure.

Using named parameters has the advantage of being more explicit in case of change of the method signature (e.g. if you add or rename a parameter). It should be the preferred way of implementing such a callback, in most cases.

Delegate with indexed variant parameters

There is another way of implementing such a callback method, directly by using the Input[] and Output[] indexed properties. It should be (a bit) faster to execute:

procedure TTestServiceOrientedArchitecture.IntSubtractVariant(
  Ctxt: TOnInterfaceStubExecuteParamsVariant);
begin
  with Ctxt do
    Output[0] := Input[0]-Input[1]; // result := n1-n2
end;

Just as with TOnInterfaceStubExecuteParamsJSON implementation, Input[] index follows the exact order of const and var parameters at method call, and Output[] index follows the exact order of var and out parameters plus any function result.

That is, if you call:

  function Subtract(n1,n2: double): double;
 ...
  MyStub.Substract(100,20);

you have in TOnInterfaceStubExecuteParamsJSON:

 Ctxt.Params = '100,20.5'; // at method call
 Ctxt.Result = '[79.5]';   // after Ctxt.Returns([..])

and in the variant arrays:

 Ctxt.Input[0] = 100;      // =n1 at method call
 Ctxt.Input[1] = 20.5;     // =n2 at method call
 Ctxt.Output[0] = 79.5;    // =result after method call

In case of additional var or out parameters, those should be added to the Output[] array before the last one, which is always the function result.

If the method is defined as a procedure and not as a function, of course there is no last Output[] item, but only var or out parameters.

Delegate with JSON parameters

You can stub a method using a JSON array as such:

  TInterfaceStub.Create(TypeInfo(ICalculator),ICalc).
    Executes('Subtract',IntSubtractJSON);
  (...)
  Check(ICalc.Substract(10.5,1.5)=9);

The callback shall be defined as such:

procedure TTestServiceOrientedArchitecture.IntSubtractJSON(
  Ctxt: TOnInterfaceStubExecuteParamsJSON);
var P: PUTF8Char;
begin // result := n1-n2
  P := pointer(Ctxt.Params);
  Ctxt.Returns([GetNextItemDouble(P)-GetNextItemDouble(P)]);
  // Ctxt.Result := '['+DoubleToStr(GetNextItemDouble(P)-GetNextItemDouble(P))+']';
end;

That is, it shall parse incoming parameters from Ctxt.Params, and store the result values as a JSON array in Ctxt.Result.

Input parameter order in Ctxt.Params follows the exact order of const and var parameters at method call, and output parameter order in Ctxt.Returns([]) or Ctxt.Result follows the exact order of var and out parameters plus any function result.

Accessing the test case when mocking

In case of mocking, you may add additional verifications within the implementation callback, as such:

  TInterfaceMock.Create(TypeInfo(ICalculator),ICalc,self).
    Executes('Subtract',IntSubtractVariant,'toto');
 (...)
procedure TTestServiceOrientedArchitecture.IntSubtractVariant(
  Ctxt: TOnInterfaceStubExecuteParamsVariant);
begin
  Ctxt.TestCase.Check(Ctxt.EventParams='toto');
  Ctxt['result'] := Ctxt['n1']-Ctxt['n2'];
end;

Here, an additional callback-private parameter containing 'toto' has been specified at TInterfaceMock definition.
Then its content is checked on the associated test case via Ctxt.Sender instance. If the caller is not a TInterfaceMock, it will raise an exception to use the Ctxt.TestCase property.

Calls tracing

As stated above, mORMot is able to log all interface calls into internal TInterfaceStub's structures. This is indeed the root feature of its "test spy" TInterfaceMockSpy.Verify() methods.

  Stub := TInterfaceStub.Create(TypeInfo(ICalculator),I).
    SetOptions([imoLogMethodCallsAndResults]);
  Check(I.Add(10,20)=0,'Default result');
  Check(Stub.LogAsText='Add(10,20)=[0]');

Here above, we retrieved the whole call stack, including returned results, as an easy to read text content.

A more complex trace verification could be defined for instance, in the context of an interface mock:

  TInterfaceMock.Create(TypeInfo(ICalculator),I,self).
    Returns('Add','30').
    Returns('Multiply',[60]).
    Returns('Multiply',[2,35],[70]).
    ExpectsCount('Multiply',qoEqualTo,2).
    ExpectsCount('Subtract',qoGreaterThan,0).
    ExpectsCount('ToTextFunc',qoLessThan,2).
    ExpectsTrace('Add',Hash32('Add(10,30)=[30]')).
    ExpectsTrace('Multiply',Hash32('Multiply(10,30)=[60],Multiply(2,35)=[70]')).
    ExpectsTrace('Multiply',[10,30],Hash32('Multiply(10,30)=[60]')).
    ExpectsTrace(Hash32('Add(10,30)=[30],Multiply(10,30)=[60],'+
      'Multiply(2,35)=[70],Subtract(2.3,1.2)=[0],ToTextFunc(2.3)=["default"]')).
    Returns('ToTextFunc',['default']);
  Check(I.Add(10,30)=30);
  Check(I.Multiply(10,30)=60);
  Check(I.Multiply(2,35)=70);
  Check(I.Subtract(2.3,1.2)=0,'Default result');
  Check(I.ToTextFunc(2.3)='default');

The ExpectsTrace() methods are able to add some checks non only about the number of calls of a method, but the order of the command executions, and the retrieved result values. Those methods expect Hash32() functions to define the hash value of the expected trace, which is a good way of minimizing data in memory or re-use a value retrieved at execution time for further regression testing.

You have even a full access to the internal execution trace, via the two TInterfaceStub.Log and LogCount properties. This will allow any validation of mocked interface calls logic, beyond ExpectsTrace() possibilities.

You can take a look at TTestServiceOrientedArchitecture.MocksAndStubs regression tests, for a whole coverage of all the internal features.

This article is part of a list of mocking/stubbing range of information:

Feedback is welcome on our forum, just as usual.