Dependency injection

A direct implementation of dependency injection at a class level can be implemented in Delphi as such:
- All external dependencies shall be defined as abstract interface;
- An external factory could be used to retrieve an interface instance, or class constructor shall receive the dependencies as parameters.

Using an external factory can be made within mORMot via TServiceFactory - see 63. In the future, we may implement automated dependency injection.

Here, we will use the more direct constructor-based pattern for a simple "forgot my password" scenario.

This is the class we want to test:

  TLoginController = class(TInterfacedObject,ILoginController)
  protected
    fUserRepository: IUserRepository;
    fSmsSender: ISmsSender;
  public
    constructor Create(const aUserRepository: IUserRepository;
      const aSmsSender: ISmsSender);
    procedure ForgotMyPassword(const UserName: RawUTF8);
  end;

The constructor will indeed inject its dependencies into its own instance:

constructor TLoginController.Create(const aUserRepository: IUserRepository;
  const aSmsSender: ISmsSender);
begin
  fUserRepository := aUserRepository;
  fSmsSender := aSmsSender;
end;

The dependencies are defined with the following two interfaces(only the needed methods are listed here, but a real interface may have much more members, but not too much, to follow the interface segregation SOLID principle):

  IUserRepository = interface(IInvokable)
    ['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}']
    function GetUserByName(const Name: RawUTF8): TUser;
    procedure Save(const User: TUser);
  end;
  ISmsSender = interface(IInvokable)
    ['{8F87CB56-5E2F-437E-B2E6-B3020835DC61}']
    function Send(const Text, Number: RawUTF8): boolean;
  end;

Note also that all those code will use a plain record as Data Transfer Object (DTO):

  TUser = record
    Name: RawUTF8;
    Password: RawUTF8;
    MobilePhoneNumber: RawUTF8;
    ID: Integer;
  end;

Here, we won't use TSQLRecord nor any other classes, just plain records, which will be used as neutral means of transmission. The difference between Data Transfer Objects and business objects or Data Access Objects (DAO) like our TSQLRecord is that a DTO does not have any behavior except for storage and retrieval of its own data. It can also be independent to the persistency layer, as implemented underneath our business domain. Using a record in Delphi ensure it won't be part of a complex business logic, but will remain used as value objects.

Now, let's come back to our TLoginController class.
Here is the method we want to test:

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

It will retrieve a TUser instance from its repository, then compute a new password, and send it via SMS to the user's mobile phone. On success, it is supposed to persist (save) the new user information to the database.

Why use fake / emulated interfaces?

Using the real implementation of IUserRepository would expect a true database to be available, with some potential issues on existing data. Similarly, the class implementing ISmsSender in the final project should better not to be called during the test phase, since sending a SMS does cost money, and we would need a true mobile phone or Internet gateway to send the password.

For our testing purpose, we only want to ensure that when the "forgot my password" scenario is executed, the user record modification is persisted to the database.

One possibility could be to define two new dedicated classes, implementing both IUserRepository and ISmsSender interfaces. But it will be obviously time consuming and error-prone. This may be a typical case when writing the test could be more complex than writing the method to be tested.

In order to maximize your ROI, and allow you to focus on your business logic, the mORMot framework proposes a simple and efficient way of creating "fake" implementations of any interface, just by defining the minimum behavior needed to run the test.

Stubs and mocks

In the book "The Art of Unit Testing" (Osherove, Roy - 2009), a distinction is drawn between stub and mock objects:

  • Stubs are the simpler of the two families of fake objects, simply implementing the same interface as the object that they represent and returning pre-arranged responses. Thus a fake object merely provides a set of method stubs. Therefore the name. In mORMot, it is created via the TInterfaceStub generator; 
  • Mocks are described as a fake object that helps decide if a test failed or passed, by verifying if an interaction on an object occurred or not. Everything else is defined as a stub. In mORMot, it is created via the TInterfaceMock generator, which will link the fake object to an existing TSynTestCase instance - see this article.

In practice, there should be only one mock per test, with as man stubs as necessary to let the test pass. Using a mocking/stubbing framework allows quick on-the-fly generation of interface with unique behavior dedicated to a particular test. In short, you define the stubs needed to let your test pass, and define one mock which will pass or fail the test depending on the feature you want to test.

Our mORmot framework follows this distinction, by defining two dedicated classes, named TInterfaceStub and TInterfaceMock, able to define easily the behavior of such classes.

Defining stubs

Let's implement our "forgot my password" scenario test.

The TSynTestCase child method could start as such:

procedure TMyTest.ForgetThePassword;
var SmsSender: ISmsSender;
    UserRepository: IUserRepository;

This is all we need: one dedicated test case method, and our two local variables, ready to be set with our stubbed / mocked implementation classes.

First of all, we will need to implement ISmsSender.Send method. We should ensure that it returns true, to indicates a successful sending.

With mORMot, it is as simple as:

  TInterfaceStub.Create(TypeInfo(ISmsSender),SmsSender).
    Returns('Send',[true]);

It will create a fake class (here called a "stub") emulating the whole ISmsSender interface, store it in the local SmsSender variable, and let its Send method return true.

What is nice with this subbing / mocking implementation is that:
- The "fluent" style of coding makes it easy to write and read the class behavior, without any actual coding in Delphi, nor class definition;
- Even if ISmsSender has a lot of methods, only Send matters for us: TInterfaceStub will create all those methods, and let them return default values, with additional line of code needed;
- Memory allocation will be handled by the framework: when SmsSender instance will be released, the associated TInterfaceStub data will also be freed (and in case a mock, any expectations will be verified).

Defining a mock

Now we will define another fake class, which may fail the test, so it is called a "mock", and the mORMot generator class will be TInterfaceMock:

  TInterfaceMock.Create(TypeInfo(IUserRepository),UserRepository,self).
    ExpectsCount('Save',qoEqualTo,1);

We provide the TMyTest instance as self to the TInterfaceMock constructor, to associate the mocking aspects with this test case. That is, any registered Expects*() rule will let TMyTest.Check() be called with a boolean condition reflecting the test validation status of every rule.

The ExpectsCount() method is indeed where mocking is defined. When the UserRepository generated instance is released, TInterfaceMock will check all the Expects*() rules, and, in this case, check that the Save method has been called exactly one time (qoEqualTo,1).

Running the test

Since we have all the expected stub and mock at hand, let's run the test itself:

  with TLoginController.Create(UserRepository,SmsSender) do
  try
    ForgotMyPassword('toto');
  finally
    Free;
  end;

That is, we run the actual implementation method, which will call our fake methods:

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

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

Feedback is welcome on our forum, just as usual.