Saturday, 2 June 2012

Mocking methods

I have been using Mock objects a lot with Delphi and I have implemented a small example and that you can find available in my personal repository on Google code. This simple example plays with the signature of the methods and uses delegates to return what we want to return. There are lots of ways to mock objects and lots of really good available frameworks: PascalMock, Ultra Basic MockObjects, DelphiMocks, etc. but sometimes we just want to mock some of the methods reintroducing a new signature. This simple example has helped me a lot in my unit testing and I hope you find it enough interesting to use it.


Basically we need an Interface to Mock which will contain all the contracts of the instance. TMock will be mocking the Interface and we need to set up the methods on the mockable class in a particular way using delegates. With this method TMock will be able to inject a new delegate and use it in our unit testing.

The declaration of TMock is quite neat:

type
  IMock = interface
  end;

  TMock<T> = class(TInterfacedObject,IMock)
  private
    FMockClass : T;
  public
    function SetUp() : T;
    constructor Create(mockingClass : T);
  end;

{ TMock<T> }

constructor TMock<T>.Create(mockingClass: T);
begin
  FMockClass := mockingClass;
end;

function TMock<T>.SetUp(): T;
begin
  Result :=  FMockClass;
end;
Then the IServer interface will have two methods which I want to mock. The delegates on the IServer will have to be extended as a property to then be able to overwrite them during the unit testing.

  //Contract definition to be Mocked.
  IServer<T, TResult> = Interface
    //Send structure
    function GetSendMessage() : TFuncParam<T, TResult>;
    procedure SetSendMessage(const Value: TFuncParam<T, TResult>);
    property SendMessage : TFuncParam<T, TResult> read GetSendMessage write SetSendMessage;
    //Receive structure
    function GetReceiveMessage() : TFunc<T>;
    procedure SetReceiveMessage(const Value: TFunc<T>);
    property ReceiveMessage : TFunc<T> read GetReceiveMessage write SetReceiveMessage;
  End;

It is really important to define the visibility of the methods on the implementation side so the mock only sees the methods we really want to redefine.

Then when the mock test is defined, it will only display the methods available to inject:
To set up the test, we only need to use the following declaration:

procedure TestTProtocolServer.TestCommunicateWithMock;
var
  ReturnValue: Boolean;
  Server: IServer<String, Boolean>;
  mock : TMock<IServer<String, Boolean>>;
begin
  Server := TServer.Create;

  mock := TMock<IServer<String, Boolean>>.Create(Server);

  //Set up the mocking methods using delegates:
  mock.SetUp().SendMessage := (function (message : string) : boolean
  begin
    result := True; //Return always true whatever the message is
  end);

  mock.SetUp().ReceiveMessage := (function () : string
  begin
    //Return the same message the server would reply
    result := 'This is the message from the server!';
  end);

  FProtocolServer := TProtocolServer.Create(mock.SetUp());
  try
    ReturnValue := FProtocolServer.Communicate;
    CheckTrue(ReturnValue, 'Communication with the Server Failed');
  finally
    Server := nil;
    mock.Free;
    FProtocolServer.Free;
    FProtocolServer := nil;
  end;
end;

With this simple method we can reuse existing code and just rewrite what we need to mock as the method we are calling depends on other objects/libraries, etc which are not testable. Note that this is not a framework, it is just a simple example where I need to mock a method and I don't want to use any of the existing frameworks. I like the way moq works and I wanted something similar (I wish we had lambda expressions, maybe one day!):

var mock = new Mock<ITest>();

mock.Setup(p => p.SendMessage(Is.Any<String>))
    .Returns(true)

It has loads of drawbacks as you will need to change the signature of your methods using delegates and sometimes it is not easy to do it, but I have been using it and it does the job.

Please, have a look at the repository and do not hesitate to leave any comment.