VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.

Today we updated Delphi Mocks to enable the Mocking of multiple interfaces. This is useful when the interface you wish to Mock is cast to another interface during testing. For example you could have the following system you wish to test.

type
  {$M+}
  IVisitor = interface;

  IElement = interface
    ['{A2F4744E-7ED3-4DE3-B1E4-5D6C256ACBF0}']
    procedure Accept(const AVisitor : IVisitor);
  end;

  IVisitor = interface
    ['{0D150F9C-909A-413E-B29E-4B869C6BC309}']
    procedure Visit(const AElement : IElement);
  end;

  IProject = interface
    ['{807AF964-E937-4A8A-A3D2-34074EF66EE8}']
    procedure Save;
    function IsDirty : boolean;
  end;

  TProject = class(TInterfacedObject, IProject, IElement)
  protected
    function IsDirty : boolean;
    procedure Accept(const AVisitor : IVisitor);
  public
    procedure Save;
  end;

  TProjectSaveCheck = class(TInterfacedObject, IVisitor)
  public
    procedure Visit(const AElement : IElement);
  end;
  {$M-}

implementation

  { TProjectSaveCheck }

  procedure TProjectSaveCheck.Visit(const AElement: IElement);
  var
    project : IProject;
  begin
    if not Supports(AElement, IProject, project) then
      raise Exception.Create('Element passed to Visit was not an IProject.');

    if project.IsDirty then
      project.Save;
  end;

The trouble previously was that when testing TProjectSaveCheck a TMock<IElement> would be required, as well as a TMock<IProject>. This is brought about by the Visit procedure requiring the IElement its passed to be an IProject for the work its going to perform.

This is now very simple with the Implement<I> method available off TMock<T>. For example to test that Save is called when IsDirty returns true, the following test could be written;

procedure TExample_InterfaceImplementTests.Implement_Multiple_Interfaces;
var
  visitorSUT : IVisitor;
  mockElement : TMock<IElement>;
begin
  //Test that when we visit a project, and its dirty, we save.

  //CREATE - The visitor system under test.
  visitorSUT := TProjectSaveCheck.Create;

  //CREATE - Element mock we require.
  mockElement := TMock<IElement>.Create;

  //SETUP - Add the IProject interface as an implementation for the mock
  mockElement.Implement<IProject>;

  //SETUP - Mock project will show as dirty and will expect to be saved.
  mockElement.Setup<IProject>.WillReturn(true).When.IsDirty;
  mockElement.Setup<IProject>.Expect.Once.When.Save;

  //TEST - Visit the mock element to see if our test works.
  visitorSUT.Visit(mockElement);

  //VERIFY - Make sure that save was indeed called.
  mockElement.VerifyAll;
end;

The Mock mockElement "implements" two interfaces IElement, and IProject. IElement is done via the constructor, and IProject is added through the Implement<I> call. The Implement<I> call adds another sub proxy to the mock object. This sub proxy then allows all the mocking functionality to be performed with the IProject interface.

To access the Setup, and Expects behaviour there are overloaded generic calls on TMock. These return the correct proxy to interact with, and generic type ISetup<I> and IExpect<I>. This is seen in the call to mockElement.Setup<IProject>. This returns a ISetup<IProject> which allows definition of what should occur when IProject is used from the Mock.

This feature is really useful when there is a great deal of casting of interfaces done in the system you wish to test. It can save having to mock base classes directly where multiple interfaces are implemented.

The way this works under the hood is fairly straight forward. TVirtualInterfaces are used when an interface is required to be mocked. This allows the capturing of method calls, and the creation of the interface instance when its required.

The Implement<I> functionality simply extends this so that when a TProxyVirtualInterface (inherited from TVirtualInterface) has QueryInterface called it also looks to its owning Proxy. If any other Proxies implement the requested interface its that TProxyVirtualInterface which is returned.

In essence this allows us to fake the Mock implementing multiple interfaces, when in fact there are a list of TVirtualInterface's all implementing a single interface.

Showing 4 Comments

Avatar
Jason Duffy Smith 5 years ago

@JeroenPluimers Thanks! On the note I have updated the post to correct the class names. Cheers!


Avatar
Jason Duffy Smith 5 years ago

@NickHodges - Currently the only limit would be memory. Apart from that, Implements<I> will allow as many interfaces as required. If memory limits are hit then I would suggest the system under test has other issues. ;)


Avatar
Nick Hodges 5 years ago

Nice! Is there a limit on the number of times you can call Implments<T>?


Avatar
Jeroen Pluimers 5 years ago

Thanks! Interesting feature.

Note:

s/TProjectSaveCheckVisitor/TProjectSaveCheck/



Comments are closed.