使用 F# 和 FakeItEasy 伪造 Return 值
Faking Return value with F# and FakeItEasy
我正在尝试使用 FakeItEasy 模拟 C# 中定义的接口
public interface IMyInterface
{
int HeartbeatInterval { get; set; }
}
在我做的 F# 测试中
let myFake = A.Fake<IMyInterface>()
A.CallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
运行 这在测试运行程序中的结果是
System.ArgumentException
Expression of type 'Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Int32]' cannot be used for return type 'System.Int32'
事实上,它似乎对任何 return 类型都这样做,例如如果 HeartbeatInterval returned 类型 foo
那么抛出的异常将针对类型 foo
而不是 System.Int32
.
我是不是做错了,还是 F# 和 FakeItEasy 不兼容?
我突然想到使用对象表达式可能是一种更简单的方法。
我大胆假设 "FakeItEasy" 中的 "Easy" 代表“Easy Over Simple”。库 说 它是 "easy",如果您从 C# 使用它,这可能是正确的。因为它显然是为与该语言一起使用而设计的。但它远非 "simple",因为它使用的是 C# 特定的语法技巧,这些技巧在视图中是隐藏的,并且在 F# 中无法正常工作。
您现在遇到的问题是两件事的结合:(1) F# 函数与 C# 不同 Func<T,R>
,以及 (2) F# 重载解析规则与 C# 不同.
CallTo
有三个重载 - 其中两个采用 Expression<_>
,第三个是 "catch-all",采用 object
。在 C# 中,如果您使用 lambda 表达式作为参数调用此方法,编译器将尽力将 lambda 表达式转换为 Expression<_>
并调用其中一个专用方法。然而,F# 并没有做出这样的努力:F# 对 C# 风格 Expression<_>
的支持非常有限,主要侧重于与 LINQ 的兼容性,并且只有在没有替代方案时才开始使用。所以在这种情况下,F# 选择调用 CallTo(object)
重载。
接下来,争论点是什么? F# 是一种非常严格和一致的语言。除了一些特殊的互操作情况外,大多数 F# 表达式都具有明确的类型,无论它们出现在什么上下文中。具体来说,fun() -> x
形式的表达式将具有 unit -> 'a
类型,其中 'a
是 x
的类型。换句话说,它是一个 F# 函数。
在运行时,F# 函数由 FSharpFunc<T,R>
类型表示,因此这就是编译器将传递给 CallTo(object)
方法的内容,该方法将对其进行查看,但无法理解见鬼,抛出异常。
要修复它,您可以为自己制作一个特殊版本的 CallTo
(我们称之为 FsCallTo
),这将强制 F# 编译器将您的 fun() -> x
表达式转换为 Expression<_>
,然后使用该方法代替 CallTo
:
// WARNING: unverified code. Should work, but I haven't checked.
type A with
// This is how you declare extension methods in F#
static member FsCallTo( e: System.Linq.Expressions.Expression<System.Func<_,_>> ) = A.CallTo( e )
let myFake = A.Fake<IMyInterface>()
// Calling FsCallTo will force F# to generate an `Expression<_>`,
// because that's what the method expects:
A.FsCallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
但是,正如您完全正确地观察到的那样,这对于模拟接口来说太麻烦了,因为 F# 已经具有完全可静态验证的运行时成本- 对象表达式形式的免费、语法上不错的替代方案:
let myFake = { new IMyInterface with
member this.HeartbeatInterval = 10
member this.HeartbeatInterval with set _ = ()
}
我完全建议和他们一起去。
我正在尝试使用 FakeItEasy 模拟 C# 中定义的接口
public interface IMyInterface
{
int HeartbeatInterval { get; set; }
}
在我做的 F# 测试中
let myFake = A.Fake<IMyInterface>()
A.CallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
运行 这在测试运行程序中的结果是
System.ArgumentException
Expression of type 'Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Int32]' cannot be used for return type 'System.Int32'
事实上,它似乎对任何 return 类型都这样做,例如如果 HeartbeatInterval returned 类型 foo
那么抛出的异常将针对类型 foo
而不是 System.Int32
.
我是不是做错了,还是 F# 和 FakeItEasy 不兼容?
我突然想到使用对象表达式可能是一种更简单的方法。
我大胆假设 "FakeItEasy" 中的 "Easy" 代表“Easy Over Simple”。库 说 它是 "easy",如果您从 C# 使用它,这可能是正确的。因为它显然是为与该语言一起使用而设计的。但它远非 "simple",因为它使用的是 C# 特定的语法技巧,这些技巧在视图中是隐藏的,并且在 F# 中无法正常工作。
您现在遇到的问题是两件事的结合:(1) F# 函数与 C# 不同 Func<T,R>
,以及 (2) F# 重载解析规则与 C# 不同.
CallTo
有三个重载 - 其中两个采用 Expression<_>
,第三个是 "catch-all",采用 object
。在 C# 中,如果您使用 lambda 表达式作为参数调用此方法,编译器将尽力将 lambda 表达式转换为 Expression<_>
并调用其中一个专用方法。然而,F# 并没有做出这样的努力:F# 对 C# 风格 Expression<_>
的支持非常有限,主要侧重于与 LINQ 的兼容性,并且只有在没有替代方案时才开始使用。所以在这种情况下,F# 选择调用 CallTo(object)
重载。
接下来,争论点是什么? F# 是一种非常严格和一致的语言。除了一些特殊的互操作情况外,大多数 F# 表达式都具有明确的类型,无论它们出现在什么上下文中。具体来说,fun() -> x
形式的表达式将具有 unit -> 'a
类型,其中 'a
是 x
的类型。换句话说,它是一个 F# 函数。
在运行时,F# 函数由 FSharpFunc<T,R>
类型表示,因此这就是编译器将传递给 CallTo(object)
方法的内容,该方法将对其进行查看,但无法理解见鬼,抛出异常。
要修复它,您可以为自己制作一个特殊版本的 CallTo
(我们称之为 FsCallTo
),这将强制 F# 编译器将您的 fun() -> x
表达式转换为 Expression<_>
,然后使用该方法代替 CallTo
:
// WARNING: unverified code. Should work, but I haven't checked.
type A with
// This is how you declare extension methods in F#
static member FsCallTo( e: System.Linq.Expressions.Expression<System.Func<_,_>> ) = A.CallTo( e )
let myFake = A.Fake<IMyInterface>()
// Calling FsCallTo will force F# to generate an `Expression<_>`,
// because that's what the method expects:
A.FsCallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
但是,正如您完全正确地观察到的那样,这对于模拟接口来说太麻烦了,因为 F# 已经具有完全可静态验证的运行时成本- 对象表达式形式的免费、语法上不错的替代方案:
let myFake = { new IMyInterface with
member this.HeartbeatInterval = 10
member this.HeartbeatInterval with set _ = ()
}
我完全建议和他们一起去。