3

I'm a newbie with Dependency Injection containers, and I am trying to get my head around using them in conjunction with Mocking.

Lets say I have a controller and a list (the model):

IBlahList = interface
  property Items[AIndex: integer]: IBlah read GetItem;
end;

IController = interface
  property List: IBlahList read GetList;
end;

The implementation of IController would look something like (note, it's in the implementaion section:

implementation

TController = class (TInterfacedObject, IController)
private
  FList: IBlahList;
  function GetList: IBlahList;

public
  constructor Create(const AList: IBlahList);

end;

And then, of course, I would register this class (as well as one for IBlahList) with the GlobalContainer:

GlobalContainer.RegisterType<TController>.Implements<IController>;

I place the TController in the implementation section, as suggested by various sources (well, Nick Hodges anyway!), so that we cannot reference the TController class directly.

Now, just say I want to test my implementation of ICollection in a unit test:

procedure TestSomething
var
  LMockList: TMock<IBlahList>;
  LController: IController;
begin
  LMockList := TMock<IBlahList>.Create;

  // Oops, I can't do this, I can't access TController
  LController := TController.Create(LMockList);

end;

So, my question is, should I move the TController class into my interface section so I can test it, or is there some other way to pass the mock IBlahList to the controller that I have yet to find?

Nat
  • 5,414
  • 26
  • 38

3 Answers3

4

If you have the concrete class in the implementation section, then you could expose a factory function (i.e. have it in the interface section) that creates an IController with the required parameters.

It makes absolutely no sense to have an implementation that can not be instantiated, IMO.

interface

...

function CreateController(AList: IBlahList): IController;

implementation

function CreateController(AList: IBlahList): IController;
begin
  Result := TController.Create(AList);
end;
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • 1
    +1 This is a good solution as well. I can only see one drawback: the application code (as opposed to the testing code) can now also directly create a controller instead of being _forced_ to go through the GlobalContainer (the mock framework). But you could of course put the function declaration in the interface section under the control of a DUNIT conditional. Which looks a lot cleaner than moving the `implementation` clause. – Marjan Venema Jun 10 '12 at 08:28
  • 1
    Wait a sec. You want the application code to go through the mock framework? Why on earth would you want that? – Rudy Velthuis Jun 11 '12 at 14:21
2

Well you probably should be using the mock framework in your test projects as well, but in these cases I usually "cheat" and move the implementation to where I need it using a DUNIT conditional variable:

// In the real app, we want the implementation and uses clauses here.
{$IFNDEF DUNIT}  
implementation
uses
  classes;
{$ENDIF}

type
  TClassUnderTest = class(TObject)
    // ...
  end;

// In test projects it is more convenient to have the implemenation and 
// uses clauses down here.
{$IFDEF DUNIT}
implementation
uses
  classes;
{$ENDIF}

Then make sure that any test projects define the DUNIT conditional var, and move any units needed by the TClassUnderTest declaration to the interface section. The latter you can do permanently or under control of the DUNIT conditional as well.

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • If you have to modify your sources in a way like that for your unit tests you are doing something wrong (in that case it is putting a class inside the implementation part of a unit) – Stefan Glienke Jun 10 '12 at 12:51
  • I don't agree. Application code and test code have their own requirements. In the application code you want to ensure that creating the class directly is simply not possible. In test code creating the class directly is often exactly what you want to do. Using conditional defines is as good a way as any to meet both requirements. Rudy's solution is cleaner, but still allows direct creation of the class. Combining the two by putting the function declaration in the interface section under control of a conditional var is the cleanest way of meeting both requirements. – Marjan Venema Jun 10 '12 at 13:06
  • Nope, they both are just hiding/trying to solve the problem which I pointed out in my answer. – Stefan Glienke Jun 10 '12 at 13:07
  • @StefanGlienke: Well yes, and I already know about dependency injection. I also live with an 1M LOC codebase that doesn't have it. Grafting it on afterwards takes some doing and is not always an option considering other constraints on dev resources. – Marjan Venema Jun 10 '12 at 13:16
  • So how do you work with classes inside the implementation part of a unit in an application that does not use DI? Working with legacy code and applying design principles to it is a different story and not the topic here. – Stefan Glienke Jun 10 '12 at 13:19
  • @StefanGlienke: The way Rudy showed with a function that returns an instance of that class. I don't see how working with legacy code and applying design principle are not both on topic here. They are not exactly mutually exclusive. Legacy code (although I hate the term for a very actively developed code base) just means you can't always go all the way when it suits you and you may have to use a couple of crutches on your way there. By the way, you did notice the first part of my answer that said to use DI and called my method a "cheat", didn't you? – Marjan Venema Jun 10 '12 at 13:26
  • The point is: we are talking about making a class private instead of public just because "someone said so" and I am pointing out why that someone was wrong. Move the class to the interface part and there you go, nothing breaks, nothing needs to be cheated just plain and simple. – Stefan Glienke Jun 10 '12 at 13:30
  • @Marjan: what use is an implementation that CAN'T be instantiated? If your implementation is a mock implementation, it should not be in that unit. If it is some kind of standard implementation, it must be accessible. The fact that - apparently - TController can only be passed to GlobalContainer in the implementation section where it is defined, shows that it very likely doesn't belong there at all. – Rudy Velthuis Jun 10 '12 at 14:29
  • @RudyVelthuis: Of course it can be instantiated. Just not directly through TSomeClass.Create, but through a meta class by some registry/factory. Of course the class would need to be registered there and the most obvious way would be through calling a registration method from the initialization section of the unit in which the class is defined. The use? Making it impossible to circumvent the registry/factory. – Marjan Venema Jun 10 '12 at 16:45
  • @Marjan: How? If the mock class is not exposed anywhere, who will generate it? Why is the mock class in the implementation section anyway? That is extreme coupling and totally against the principles of decoupling. Why is it not in a separate unit, where it doesn't matter if it is exposed or not. If it is not a mock class, it should be exposed. And if it can be instantiated using a registry, then what is the problem? – Rudy Velthuis Jun 11 '12 at 13:40
  • @RudyVelthuis: you are pretty intelligent. I am sure that when you re-read all the comments, you'll get that I am not arguing a single case, but multiple approaches to several goals under different constraints and requirements. Each approach has benefits and drawbacks, none can satisfy all goals at the same time. – Marjan Venema Jun 11 '12 at 19:20
2

I can just say: don't listen to Nick in that case.

Putting a class inside the implementation part of a unit just has disadvantages and you are facing one of them.

The whole point of using dependency injection is to decouple pieces of your code.

Now you removed the static dependency of TController and some class that implements IBlahList but you pulled in another (and much worse imo) dependency: the dependency on the DI container.

Don't put the class inside the implementation part of a unit just to prevent someone from directly creating it in your production code. Also don't put in the dependency on the DI container into that unit.

A much better approach is to have 3 units: interface, class, registration.

Edit: I suggest reading this article and pay attention to the underlined parts: http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html

Edit2 - added some pseudo code to show what I mean. The unit test code could exactly be as in the question.

unit Interfaces;

interface

type
  IBlahList = interface
    property Items[AIndex: integer]: IBlah read GetItem;
  end;

  IController = interface
    property List: IBlahList read GetList;
  end;

implementation

end.

-

unit Controller;

interface

uses
  Classes,
  Interfaces;

type
  TController = class (TInterfacedObject, IController)
  private
    FList: IBlahList;
    function GetList: IBlahList;
  public
    constructor Create(const AList: IBlahList);
  end;

implementation

...

end.

-

unit Registration;

interface

implementation

uses
  Interfaces,
  Controller,
  Spring.Container;

initialization
  GlobalContainer.RegisterType<TController>.Implements<IController>;

end.
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • While this is good general adivce and the link is interesting, neither shows how to implement it and how to avoid pulling in a dependency on the DI container. Any chance of a bare bones example of how this can be achieved in __both__ application and test code? – Marjan Venema Jun 10 '12 at 13:18
  • @Marjan: ISTM that many people, you included, blindly apply the principles laid out some textbook, instead of thinking about how it can help you. I fully agree with Stefan that the implementation of a mock class should never be done in the same unit as the interface is declared. That provides more coupling than you'd ever like. Put each in their own unit. The mock class must know the interface, and the reigstration must know both. But the interface unit should not be coupled with the mock class. Stefan shows you how. – Rudy Velthuis Jun 11 '12 at 13:32
  • FWIW, I really wonder how the IBlahList in the original example compiles. Apart form the fact that we don't know IBlah, we also don't see any *methods*, and ISTM that they must be present for a property to function. This shows me that we don't see the real code. – Rudy Velthuis Jun 11 '12 at 13:35
  • @RudyVelthuis: Sorry Rudy, but you are making unwarranted assumptions. Whether or not I apply some principle or not does not mean I do it blindly... I wouldn't presume to know whether other people apply them blindly or not. – Marjan Venema Jun 11 '12 at 19:11
  • @Stefan: thanks for the elaboration. It will help many people understand your point more clearly. – Marjan Venema Jun 11 '12 at 19:14
  • @Marjan: OK, blindly or not, it is IMO against a lot of design principles to put a mock class in the same unit as the interface/code you are testing. The mocking and testing code should never be part of the final application you deliver to your customers, IMO. – Rudy Velthuis Jun 11 '12 at 20:40
  • @RudyVelthuis: Oh I agree with that. Test code should be in separate units. Dependency injection registration is something else. We need to be able to switch implementing classes simply by switching units without having to amend any registration code. Use case: different implementations of some interface or base class in different projects of the same software suite, possibly under control of release/debug/other conditional vars. That necesitates registration of the class with the DI/factory within the unit itself, regardless of mocking for testing. – Marjan Venema Jun 12 '12 at 06:17
  • @MarjanVenema No, it does not, you can still move the registration to a separate unit and put it into compiler directives or whatever there. – Stefan Glienke Jun 12 '12 at 07:08
  • @StefanGlienke: That would defeat the goal of switching implementations simply by taking out unit A and adding unit B. At the very least you would then have to change the dproj (apart from what the IDE does automatically) or an inc file as well to change the conditional vars that control the registration. (And yes, we do have all used units in the dpr). Plus: we don't want everything under control of conditional vars. In fact, we are trying to get rid of most if not all except release/debug/dunit. – Marjan Venema Jun 12 '12 at 07:27
  • IMO, *nothing* else should be in the same unit. If you want to perform dependency injection, then you do it to reduce dependencies and coupling and certainly not to put a new dependency in the same unit. I am not very familiar with dependency injection frameworks, but this approach screams silently in my head: help, something is wrong! – Rudy Velthuis Jun 12 '12 at 21:41
  • @Rudy The example I presented was indeed not real code, but included to be illustrative. I didn't want the question to get too long, so left out bits I didn't think were important. – Nat Jun 15 '12 at 04:04
  • @StefanGlienke You bring up many valid points... I do like the idea of separating out the registration code. I will ponder on this. – Nat Jun 15 '12 at 04:10