Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing singleton instances from Delphi project

I am trying to make my first project that can be unit tested. And it is amazing how I have to rewire my brain of some vicious coding styles.

This article got me the attention that Singletons are pathological liars

I am not trying to be radical on that, but I am used to an artifact that I am not sure how can i get rid of it

example:

initialization
  ModelFactory.RegisterFactoryMethod('standard.contasmovimento',
    function(AParam: Variant) : TModel
    begin
      result := TModelContasMovimento.Create(AParam);
    end);

end.

ModelFactory is a singleton, defined on its unit and part of the uses clause of this unit.

In my MVP structure I define each of the Models, Views and Presenters in its own unit (1 class 1 unit). All these units are available to be used, according the needs of each project. So, I use it like a parts catalog, according the project I add the units to the project and it gets automatically registered and ready to be used from the factories.

To solve the singleton problem i was thinking in move them to a framework class, so I could create the object at one point and then could use dependency injection to pass the framework object. All the factories and other environment stuff are sit there:

TMyFramework = class
  FModelFactory: IModelFactory;
  FViewFactory: IViewFactory;
  FPresenterFactory: IPresenterFactory;

  property ModeFactory: IModelFactory read FModelFactory;
  ...

My ideia is remove the singletons in a way that I can mock them in a test unit. With singletons in place I cant remove them easily for testing.

But that will make me loose the automatic initialization of each unit, an I rely on that to add the units. I don't want to manually create a list of available classes.

Is there a way to solve this situation?

like image 423
Eduardo Elias Avatar asked May 14 '14 00:05

Eduardo Elias


Video Answer


2 Answers

For unit testing to get off the ground without a complete rewrite of all the singletons you have, I have found that just adding the ability to Free and re-Create the singletons is usually more than enough to get you started.

Assuming the singletons are instantiated in an initialization section (Ban those. They are the bane of any unit test. Go for separate registration and initialization units.), you can simply add two procedures to the interface section. Put them between conditional defines if you don't want other units in your normal project to use them:

{$IFDEF DUNIT}
procedure InstantiateMySingleton;
procedure FreeMySingleton;
{$ENDIF}

Move the code that you now have in your initialization and finalization sections to the implementation of these procedures and just call them from the initialization and finalization.

procedure InstantiateMySingleton;
begin
   // ...
end;

procedure FreeMySingleton;
begin
   // ...
end;

initialization
  InstantiateMySingleton;
finalization
  FreeMySingleton;

With this done you can start to use the InstantiateMySingleton and FreeMySingleton in your unit tests' setup and teardown methods.

All you have then left to do is to make sure that creating and destroying the singleton doesn't leak memory and is something that can actually be repeated with the same functional results every single time. One thing I have found that helps in ensuring this is to use the GUI runner and run the whole test suite twice (without exiting the GUI of course!). If there are tests that succeed on the first run and fail on the second run, you have problems in the initialization or finalization of your singleton.

like image 54
Marjan Venema Avatar answered Nov 15 '22 17:11

Marjan Venema


It depends if you need the instance in another class or the factory to instantiate the class at a later point. In the first case you can simply pass the constructed instance as dependency. In the other case you pass the factory. In both cases you are using dependency injection instead of looking it up in some other unit ("Don't look for things but ask for things").

Of course this requires some wire up code for the dependency ("poor man's DI") or the use of a DI container.

Dependency injection really is the only way to make things testable in a clean way because you just simply pass mocks to the SUT.

like image 38
Stefan Glienke Avatar answered Nov 15 '22 19:11

Stefan Glienke