Delphi Spring Mocking:在 `as` 操作中无效的转换 -- 我该如何解决这个问题?
Delphi Spring Mocking: Invalid Cast at `as` operation -- How do I solve this?
我想测试将一个接口强制转换为另一个接口的方法。转换是有效的,因为一个接口是由另一个接口派生的。不幸的是,我在标记的行收到错误。我已经尝试模拟 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 模拟比你想象的更强大。
模拟自动 return 从方法 returning 一个可模拟接口 (*) 中模拟 - 并且始终是它的相同实例。这意味着对于工厂模拟,您不需要指定任何期望。您只需要获取 mock returned 来指定它的行为。
还在那里发现了一个小错误 - 它会在任何界面上尝试此操作,而不管其“可模拟性”(这是一个词吗?^^)。我将在这里添加一张支票。然后,如果它不是可模拟的,那么如果您真的尝试将其作为模拟来抓取,稍后会发生错误。
为了让 mock 也支持其他接口,您只需告诉它即可。这遵循与在对象中实现接口相同的行为。如果您仅在 class 中实现 IYetAnotherIntf
并将其存储在该类型的接口变量中,然后在其上调用 as
、Supports
或 QueryInterface
,它将失败。
这是完整的代码 - fwiw 模拟是自动初始化的,因此您不必调用 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;
在准备这个答案时,我发现了我描述的问题,因为我想证明您可以在另一个接口上定义不同的行为,就像在 classes 中实现接口一样。正如我所写的那样,我将很快对此进行调查。我认为它是接口拦截器上普遍缺少的功能,因为现有的拦截器只是被传递给额外处理的接口,这在此处是不需要的。
2021 年 4 月 12 日更新:上述两个错误现已修复:
- 方法return只有当接口有方法信息
时,接口才会自动return模拟
- 在 mock 上支持其他接口时,每个接口都有自己的行为规范
我想测试将一个接口强制转换为另一个接口的方法。转换是有效的,因为一个接口是由另一个接口派生的。不幸的是,我在标记的行收到错误。我已经尝试模拟 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 模拟比你想象的更强大。 模拟自动 return 从方法 returning 一个可模拟接口 (*) 中模拟 - 并且始终是它的相同实例。这意味着对于工厂模拟,您不需要指定任何期望。您只需要获取 mock returned 来指定它的行为。 还在那里发现了一个小错误 - 它会在任何界面上尝试此操作,而不管其“可模拟性”(这是一个词吗?^^)。我将在这里添加一张支票。然后,如果它不是可模拟的,那么如果您真的尝试将其作为模拟来抓取,稍后会发生错误。
为了让 mock 也支持其他接口,您只需告诉它即可。这遵循与在对象中实现接口相同的行为。如果您仅在 class 中实现 IYetAnotherIntf
并将其存储在该类型的接口变量中,然后在其上调用 as
、Supports
或 QueryInterface
,它将失败。
这是完整的代码 - fwiw 模拟是自动初始化的,因此您不必调用 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;
在准备这个答案时,我发现了我描述的问题,因为我想证明您可以在另一个接口上定义不同的行为,就像在 classes 中实现接口一样。正如我所写的那样,我将很快对此进行调查。我认为它是接口拦截器上普遍缺少的功能,因为现有的拦截器只是被传递给额外处理的接口,这在此处是不需要的。
2021 年 4 月 12 日更新:上述两个错误现已修复:
- 方法return只有当接口有方法信息 时,接口才会自动return模拟
- 在 mock 上支持其他接口时,每个接口都有自己的行为规范