2

A colleague of mine needed to test whether some F# functions are called or not a given number of times.

In Moq, you can usually do that if you have a class with virtual members or an interface (unless if this has changed, but it doesn't seem to be the case), but 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 and I'm not sure the F# support is actually properly implemented.

We ended up creating a CallCounter type, which would hold the function to invoke and a variable counting the number of times this function has been invoked (a bit similar to this answer but with an actual type).

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 @>

I was wondering if there was something out of the box in Moq to achieve the same sort of thing?


I mean without having to rely on creating a placeholder interface with an impl. using object expressions just for the grand sake to hold the function to invoke, in order to make it compliant with Moq, like below:

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)
Natalie Perret
  • 8,013
  • 12
  • 66
  • 129

1 Answers1

4

Unless I'm missing something, I don't see why you need any objects or interfaces. Since it's all just functions, you could make funDep just increment a locally declared mutable counter as a side effect:

[<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 frameworks may be occasionally useful in F# in some corner cases, but in general, I see their whole purpose as compensating for the deficiencies of OOP. Unless you're interacting with some .NET library that uses objects and interfaces, chances are you can do without them.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • I agree with you, I just had hard times convincing though. – Natalie Perret Jun 20 '20 at 12:47
  • Also the example you're showing is one of the thing we were already doing in a few places, anyway we're trying to formalize things (ie. guidelines). Thanks for your answer! – Natalie Perret Jun 20 '20 at 14:55
  • Soooo... Is that the answer you were looking for? Or did I misunderstand the question? – Fyodor Soikin Jun 20 '20 at 14:56
  • Well, let's put it this way, that's the answer I already had in mind =] – Natalie Perret Jun 20 '20 at 15:01
  • So then... If you already knew how to do this without a surrogate interrace, why did you ask the question? – Fyodor Soikin Jun 20 '20 at 15:03
  • I do lack a spine when I'm trying defend a point and I'm easily bent by others opinions. So I just wanted to confirm that I was not the only one to think that Moq is only for some very specific cases and usually we can (and should) usually get by in F# without C# mocking libraries, which my team was doing for about a year already. We recently onboarded someone and that caused me to have some doubts about what I thought was granted for quite a while. – Natalie Perret Jun 20 '20 at 15:07
  • This is a very helpful answer! It's incredible how simple this is. – Matthew MacFarland Nov 14 '22 at 14:01