If you cannot mock TFoo1
, you cannot mock TFoo1
. Right now, TFoo2
is responsible for creating all instances of TFoo1
, but if that is not the main goal of TFoo2
, then it will really do hard unit testing.
One solution is, as you suggested, to pass TFoo2
any TFoo1
instances that it needs. This can complicate all your current code, which already calls TFoo2
methods. Another way that is a bit more module friendly is to provide a factory for TFoo1
. factory can be as simple as a function pointer, or it can be an entire class. In Delphi, metaclasses can also serve as factories. Pass the factory to TFoo2
when it is created, and whenever TFoo2
needs an instance of TFoo1
, it can call factory.
To reduce the changes in the rest of your code, you can make the factory parameter the default in the TFoo2
constructor. Then you do not need to change the application code. Just modify the device test code to provide a non-default factory argument.
Whatever you do, you need to make TFoo1.DoSomething1
virtual, otherwise the mockery will be useless.
Using metaclasses, your code might look like this:
type TFoo1 = class procedure DoSomethign1; virtual; end; TFoo1Class = class of TFoo1; TFoo2 = class private oFoo1 : TFoo1; FFoo1Factory: TFoo1Class; public constructor Create(AFoo1Factory: TFoo1Class = nil); end; constructor TFoo2.Create; begin inherited Create; FFoo1Factory := AFoo1Factory; if not Assigned(FFoo1Factory) then FFoo1Factory := TFoo1; oFoo1 := FFoo1Factory.Create; end;
Now your device testing code can provide a mock version of TFoo1
and pass it when creating TFoo2
:
type TMockFoo1 = class(TFoo1) procedure DoSomething1; override; end; procedure TMockFoo1.DoSomething1; begin // TODO: Pretend to access Web service end; procedure TestFoo2; var Foo2: TFoo2; begin Foo2 := TFoo2.Create(TMockFoo1); end;
Many examples of metaclasses give the base class a virtual constructor, but this is not strictly necessary. You only need to have a virtual constructor, if the constructor needs to be called practically - if the descendant constructor needs to do something with the constructor parameters that the base class does not do yet. If a descendant ( TMockFoo1
, in this case) performs all the same functions as its ancestor, then the constructor does not have to be virtual. (Also remember that AfterConstruction
already virtual, so thereβs another way to force the descendant to perform additional operations without having to create a virtual constructor.)