Moq:如何在没有占位符界面的情况下模拟 F# 中的函数?

Moq: How to mock a function in F# without a placeholder interface?

我的一位同事需要测试某些 F# 函数是否被调用了给定的次数。

在 Moq 中,如果你有一个带有虚拟成员或接口的 class,你通常可以这样做(除非这已经改变,但似乎并非如此),但是 afaik you can hardly mock static methods with Moq for example, which in most cases is how F# functions are compiled to, at least from an IL standpoint. Or, would require to use another library to do so like AutoFake or Pose 而且我不确定 F# 支持是否真的正确实现了。

我们最终创建了一个 CallCounter 类型,它将保存要调用的函数和一个计算该函数被调用次数的变量(有点类似于 this answer 但有一个实际类型)。

module Tests

open Foq
open Xunit
open Swensen.Unquote


type CallCounter<'Input, 'Output>(f: 'Input -> 'Output) =
    let mutable count = 0
    member this.Count = count
    member this.Invoke(input) =
        count <- count + 1
        f input

type CallOutputs<'Input, 'Output>(f: 'Input -> 'Output) =
    let outputs = ResizeArray()
    member this.Outputs =
        List.ofSeq outputs
    member this.Invoke(input) =
        let output = f input
        outputs.Add(output)
        output

let callFunDepTwice (funDep: unit -> int32) =
    sprintf "%A|%A" (funDep()) (funDep())

[<Fact>]
let ``callFunDepTwice should work1``() =
    let funDep = fun() -> 42
    let funDepCounter = CallCounter(funDep)
    let actual = callFunDepTwice funDepCounter.Invoke
    test <@ actual = sprintf "%A|%A" 42 42 @>
    test <@ funDepCounter.Count = 2 @>

我想知道 Moq 中是否有开箱即用的东西来实现同样的事情?


,如下图:

type ISurrogate<'Input, 'Output> =
    abstract member Invoke: 'Input -> 'Output

[<Fact>]
let ``callFunDepTwice should work2``() =
    let mockConf = Mock<ISurrogate<unit, int32>>().Setup(fun x -> <@ x.Invoke() @>).Returns(42)
    let mock = mockConf.Create()
    let actual = callFunDepTwice mock.Invoke
    test <@ actual = sprintf "%A|%A" 42 42 @>
    Mock.Verify(<@ mock.Invoke() @>, Times.exactly 2)

除非我遗漏了什么,否则我不明白您为什么需要任何对象或接口。因为它只是函数,你可以让 funDep 只是增加一个本地声明的可变计数器作为副作用:

[<Fact>] let ``callFunDepTwice should work1``() = 
    let mutable count = 0
    let funDep = fun() -> count <- count + 1; 42 
    let actual = callFunDepTwice funDep
    test <@ actual = sprintf "%A|%A" 42 42 @>   
    test <@ count = 2 @>

Mocking 框架在 F# 中偶尔会在某些极端情况下有用,但总的来说,我认为它们的全部目的是弥补 OOP 的不足。除非您正在与某些使用对象和接口的 .NET 库进行交互,否则您很可能没有它们。