VSoftTechnologies/Delphi-Mocks

Access Violation when creating a stub in a different method

jachguate opened this issue · 2 comments

I'm not really sure if this is a Delphi (maybe TVirtualInterface) or a Delphi-Mocks bug, but a TStub is prematurely freed when it is created on a different method.

I came to the conclusion after hours of debugging.... to make a long story short, let's the code talk for me:

Given this interfaces:

  IBar = interface(IInvokable)
    procedure DoNothing;
    procedure DoNothingWithParam(X: Integer);
    function GetNothing: string;
  end;

  IFoo = interface(IInvokable)
    procedure SetBar(Value: IBar);
    function GetBar: IBar;
    property Bar: IBar read GetBar write SetBar;
    procedure UseBar;
  end;

I have a TestFoo TestCase, with this methods (DUnit)

//private methods, just to show the _bug_
procedure TestIFoo.SetupBar;
begin
  //this creates a real object implementing the Interface
  FIFoo.Bar := UBarImpl.GetBar;
end;

procedure TestIFoo.SetupStub;
begin
  FIFoo.Bar := TStub<IBar>.Create;
end;

//published tests, which call the above methods:

procedure TestIFoo.TestUseBar_SetupBar_WithCall;
begin
  SetupBar;
  FIFoo.UseBar;
end;

procedure TestIFoo.TestUseStub_SetupStub_WithCall;
begin
  SetupStub;
  FIFoo.UseBar;
end;

The TFoo relevant methods looks like this:

procedure TFoo.SetBar(Value: IBar);
begin
  FBar := Value;
end;

procedure TFoo.UseBar;
begin
  CheckConfigured;
  if FBar.GetNothing = '' then
    FBar.DoNothingWithParam(1);
end;

image

As you can see, the TestUseBar_SetupBar_WithCall test (using real object implementing the interface) is succeeding, while the TestUseStub_SetupStub_WithCall (using a TStub) is failing with an EAccessViolation error.

The AccessViolation occurs in the first interface use inside the UseBar method.

What I can see is the TStub is being prematurely freed when the TestIFoo.SetupStub ends, and that's because the Reference count is not being incremented (no call to _AddRef) when the interface is assigned to the FBar field inside the TFoo.SetBar method, whereas the real object reference count gets incremented on that same line.

Hi,

I suspect this is the problem

procedure TestIFoo.SetupStub;
begin
  FIFoo.Bar := TStub<IBar>.Create;
end;

TStub is record which holds a reference to the interface, but your stub is going out of scope immediately because you don't hold a reference to the TStub anywhere. Declare a field on your test class for the Stub to hold the stub

TestIFoo = class(TTestCase)
private
  FBarSub : TStub<IBar>;
...
procedure TestIFoo.SetupStub;
begin
  FBarStub := TStub<IBar>.Create;
  FIFoo.Bar := FBarStub;
end;

Well... I suppose you're right. I mistakenly thought that holding a reference to the Interface was enough to keep it alive, but I'm learning now that this is not the case...

I'm actually testing a class that depends on a large set of interfaces, so I was trying to create Stubs for all in a method and call that code in a set of my tests, you know basic DRY.

Now I'm wondering if keeping this large list of references in the test could cause more problems that the benefit of doing this, but I'm still don't want to copy/paste the code to create all the stubs to every method of this large Test Case class.