Moq: как имитировать функцию в F # без интерфейса заполнителя? - PullRequest
1 голос
/ 20 июня 2020

Моему коллеге нужно было проверить, вызываются ли некоторые функции F # заданное количество раз или нет.

В Moq это обычно можно сделать, если у вас есть класс с виртуальными членами или интерфейс ( если это не изменилось, но, похоже, это не так), но afaik вы вряд ли сможете имитировать методы stati c с помощью Moq , например, который в большинстве случаев соответствует F # функции компилируются в , по крайней мере, с точки зрения IL. Или для этого потребуется использовать другую библиотеку, например AutoFake или Pose , и я не уверен, что поддержка F # действительно реализована должным образом.

Мы закончили создание типа CallCounter, который будет содержать вызываемую функцию и переменную, подсчитывающую, сколько раз эта функция была вызвана (немного похоже на этот ответ , но с фактическим типом).

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 для достижения того же самого?


Я имею в виду без необходимости полагаться на создание интерфейса заполнителя с имп. использование объектных выражений просто ради сохранения вызываемой функции, чтобы сделать ее совместимой с 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)

1 Ответ

2 голосов
/ 20 июня 2020

Если я чего-то не упускаю, я не понимаю, зачем вам нужны какие-либо объекты или интерфейсы. Поскольку это всего лишь функции, вы можете сделать 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 @>

Мокирующие фреймворки могут быть иногда полезны в F # в некоторых угловых случаях, но в общем, я вижу всю их цель в компенсации недостатков OOP. Если вы не взаимодействуете с какой-либо библиотекой. NET, которая использует объекты и интерфейсы, скорее всего, вы можете обойтись без них.

...