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