Delphi Spring Mocking:在"as"操作时强制转换无效--如何解决此问题



我想测试一个方法,其中一个接口被转换为另一个接口。强制转换是有效的,因为一个接口是由另一个派生的。不幸的是,我在标记线上收到一个错误。我已经准备好模拟QueryInterface,但函数没有被调用,错误仍然存在。是否有可能在Spring.Mocking中处理此问题?

坦克和保持健康

unit Main;
{$M+}
interface
procedure Execute;
implementation
uses
Spring.Mocking,
System.SysUtils;
type
TRefFunc = reference to function: Boolean;
IHelper = interface
['{7950E166-1C93-47E4-8575-6B2CCEE05304}']
end;
IIntfToMock = interface
['{8D85A1CD-51E6-4135-B0E9-3E732400BA25}']
function DoSth(const AHelper: IHelper; const ARef: TRefFunc): Boolean;
end;
IYetAnotherIntf = interface(IIntfToMock)
['{95B54D3B-F573-4957-BDB3-367144270C3B}']
end;
IIntfProvider = interface
['{8B3E4B7B-1B2D-4E1F-942D-7E6EB4B9B585}']
function YetAnotherIntfFactory: IYetAnotherIntf;
end;
TClassToTest = class
private
FIntfProvider: IIntfProvider;
public
function MethodeToTest: Boolean;
constructor Create(const AIntfProvider: IIntfProvider);
end;
procedure Execute;
var
IntfMock : Mock<IIntfToMock>;
YetiMock : Mock<IYetAnotherIntf>;
ProvMock : Mock<IIntfProvider>;
Instance : TClassToTest;
OutObj   : Pointer;
begin
IntfMock := Mock<IIntfToMock>.Create();
YetiMock := Mock<IYetAnotherIntf>.Create();
YetiMock.Setup.Returns(True).When.DoSth(Arg.IsAny<IHelper>, Arg.IsAny<TRefFunc>());
{
// Just a try. Did not work...
YetiMock.Setup.Executes(
function (const ACallInfo: TCallInfo): TValue
begin
ACallInfo.Args[1].From(IIntfToMock(IntfMock));
Result := TValue.From(True);
end
).When.QueryInterface(IIntfToMock, OutObj);
}
ProvMock := Mock<IIntfProvider>.Create();
ProvMock.Setup.Returns(TValue.From(IYetAnotherIntf(YetiMock))).When.YetAnotherIntfFactory;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
{ TClassToTest }
constructor TClassToTest.Create(const AIntfProvider: IIntfProvider);
begin
Self.FIntfProvider := AIntfProvider;
end;
function TClassToTest.MethodeToTest: Boolean;
var
Instance   : IIntfToMock;
YetAnother : IYetAnotherIntf;
begin
//
Result := False;
try
Instance := Self.FIntfProvider.YetAnotherIntfFactory;
Instance.DoSth(nil, nil);
YetAnother := Self.FIntfProvider.YetAnotherIntfFactory;
Instance := YetAnother; // works
Instance := IIntfToMock(YetAnother);  // works
Instance := YetAnother as IIntfToMock; // BOOM: EIntfCastError
Result := True;
except
end;
end;
end.

Spring Mock比您想象的更强大。mock自动从返回mockable接口(*(的方法中返回mock,并且始终返回它的同一实例。这意味着对于工厂mock,您不需要指定任何期望值。您只需要获取返回的mock来指定其行为。还发现了一个小错误——它在任何接口上都会尝试这个,不管它的";可模仿性";(这是一个词吗?^^(。我会在这里加一张支票。然后,如果它不可模拟,如果你真的试图将其作为模拟来获取,那么稍后就会发生错误。

为了让mock也支持其他接口,你只需要告诉它。这与在对象中实现接口的行为相同。如果只在类中实现IYetAnotherIntf并将其存储在该类型的接口变量中,但随后对其调用asSupportsQueryInterface,则它将失败。

以下是整个代码-fwiw mock是自动初始化的,因此您不必调用Create,这很好地将代码简化为其本质:行为规范。此外,如果你根本不关心任何参数,你可以把它写得短一点。

procedure Execute;
var
ProvMock: Mock<IIntfProvider>;
YetiMock: Mock<IYetAnotherIntf>;
Instance: TClassToTest;
begin
// using type inference here - <IYetAnotherIntf> on From not necessary
YetiMock := Mock.From(ProvMock.Instance.YetAnotherIntfFactory);
// lets make the behavior strict here 
// so it does not return False when there is no match
YetiMock.Behavior := TMockBehavior.Strict;
YetiMock.Setup.Returns(True).When(Args.Any).DoSth(nil, nil);
// this will internally add the IIntfToMock to the intercepted interfaces
// as it returns a Mock<IIntfToMock> we can also specify its behavior 
// more about this particular case below
YetiMock.AsType<IIntfToMock>;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
function TClassToTest.MethodeToTest: Boolean;
var
Helper: THelper;
RefFunc: TRefFunc;
Instance: IIntfToMock;
YetAnother: IYetAnotherIntf;
begin
Result := False;
try
// just using some variables for this demo 
// to verify that arg matching is working
Helper := THelper.Create;
RefFunc := function: Boolean begin Result := False end;
Instance := FIntfProvider.YetAnotherIntfFactory;
Assert(Instance.DoSth(Helper, RefFunc));
YetAnother := FIntfProvider.YetAnotherIntfFactory;
Assert(YetAnother.DoSth(Helper, RefFunc));
// same as directly assign YetAnotherIntfFactory
Instance := YetAnother;
Assert(Instance.DoSth(Helper, RefFunc));
// same as before, direct assignment no interface cast via QueryInterface
Instance := IIntfToMock(YetAnother);
Assert(Instance.DoSth(Helper, RefFunc));
// QueryInterface "cast" - the interface interceptor internally needs to know
// that it also should handle that interface
Instance := YetAnother as IIntfToMock;
// the following also returns true currently but I think this is a defect
// internally setup for a mock returned via the AsType goes to the same
// interceptor and thus finds the expectation defined on the mock it was
// called on. That means you cannot specify derived behavior on such a mock
// or even worse if they are completely unrelated types but have identical
// methods they would interfer with each other - I will look into this
Assert(Instance.DoSth(Helper, RefFunc));
Result := True;
except
end;
end;

在准备这个答案时,我发现了我所描述的问题,因为我想证明你可以在其他接口上定义不同的行为,就像在类中实现接口一样。正如我所写的,我很快就会对此进行调查。我认为这是接口拦截器上普遍缺少的功能,因为现有的拦截器只是被传递到额外处理的接口,这在这里是不需要的。

更新12.04.2021:上述两个错误现已修复:

  • 返回接口的方法只有在接口有方法信息时才会自动返回mock
  • 当在mock上支持其他接口时,每个接口都有自己的行为规范

相关内容

  • 没有找到相关文章

最新更新