How do I mock a class that is responsible for instantiating another class?

Pay attention to the following code:

type TFoo1 = class public procedure DoSomething1; end; TFoo2 = class private oFoo1 : TFoo1; public procedure DoSomething2; procedure DoSomething3; constructor Create; destructor Destroy; override; end; procedure TFoo1.DoSomething1; begin ShowMessage('TFoo1'); end; constructor TFoo2.Create; begin oFoo1 := TFoo1.Create; end; destructor TFoo2.Destroy; begin oFoo1.Free; inherited; end; procedure TFoo2.DoSomething2; begin oFoo1.DoSomething1; end; procedure TFoo2.DoSomething3; var oFoo1 : TFoo1; begin oFoo1 := TFoo1.Create; try oFoo1.DoSomething1; finally oFoo1.Free; end; end; 

I am creating unit tests for a class and I am stuck on it. My questions are about the best way to mock objects and the design pattern I should use. I have not tested class I unit testing.

  • In the following example, I need to make fun of Foo1 because it sends a request to a web service that I cannot call during my unit testing. But Foo1 is created by the TFoo2 constructor, and I can't make fun of it. What should I do in this case? Should I change the constructor of TFoo2 to accept the object Foo1 , like this?

     constructor TFoo2.Create(aFoo1 : TFoo1) begin oFoo1 := aFoo1; end; 

    Is there a design template that says that we need to pass all the objects that the class depends on, for example, the example above?

  • The TFoo2.DoSomething3 method creates the object Foo1 , and then frees it. Should I also change this code to pass the object to Foo1 ?

     procedure TFoo2.DoSomething3(aFoo1 : TFoo1); begin aFoo1 := aFoo1.DoSomething1; end; 
  • Is there a design template that supports the suggestions I made? If so, I can tell all the developers in the company I work for, why we need to follow the XXX template to make unit testing easier.

+4
source share
1 answer

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.)

+8
source

All Articles