使用F#和FakeItEasy伪造返回值

FSh*_*ust 5 f# unit-testing fakeiteasy

我试图使用FakeItEasy来模拟在C#中定义的接口

public interface IMyInterface
{
    int HeartbeatInterval { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在F#测试我做

let myFake = A.Fake<IMyInterface>()

A.CallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
Run Code Online (Sandbox Code Playgroud)

在测试运行器中运行此结果

System.ArgumentException Expression of type 'Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Int32]' cannot be used for return type 'System.Int32'

事实上,它似乎是为任何返回类型执行此操作,例如,如果HeartbeatInterval返回类型,foo则抛出的异常将是类型foo而不是System.Int32.

我做错了还是F#和FakeItEasy之间有些不兼容?

我已经想到使用Object Expression可能是一种更容易的方法.

Fyo*_*kin 7

我冒昧地假设"FakeItEasy"中的" Easy "代表" Easy Over Simple ".图书馆这很"简单",如果你是用C#来表示它可能就是这样.因为它非常明显地设计用于该语言.但它远非"简单",因为它使用隐藏在视图中的C#特定语法技巧,并且在F#井中不起作用.

你现在得到的具体问题是两件事的结合:(1)F#函数与C#不同Func<T,R>,(2)F#重载决策规则与C#不同.

有三个重载CallTo- 其中两个采取一个Expression<_>,第三个是"全能",采取一个object.在C#中,如果使用lambda-expression作为参数调用此方法,编译器将尽力将lambda-expression转换为an 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表达式转换为a 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
Run Code Online (Sandbox Code Playgroud)

但是,正如您已经完全正确地观察到的那样,这对于模拟接口来说太麻烦了,因为F#已经以对象表达式的形式具有完全静态可验证的,运行时成本自由的,语法上不错的替代方案:

let myFake = { new IMyInterface with 
                 member this.HeartbeatInterval = 10
                 member this.HeartbeatInterval with set _ = ()
             }
Run Code Online (Sandbox Code Playgroud)

我完全建议改用它们.