.net - 将 Foq 与 F# 函数类型一起使用

标签 .net unit-testing f# mocking foq

我正在使用 F# 类型定义来防止我的函数之间的硬依赖,例如

type IType1 = int -> int
type IType2 = int-> string

let func1 (i : int) : int = i * i
let func2 (i : int) : string = i |> string

let higherFunc (dep1 : IType1) (dep2 : IType2) (input : int) : string =
    input |> dep1 |> dep2

let curriedFunc = higherFunc func1 func2
let x = curriedFunc 2

输出 x:“4”

显然,这是非常人为和简单的,但想象一下依赖项是解析器和排序器或其他任何东西。我正在编写的更小的功能。

我正在尝试使用 Foq 来帮助我的单元测试装置。这是我正确使用 F# 的第一周,我很难弄清楚如何配置这些类型的模拟。

有两点值得一提:

1 - 如果我使用抽象类,我可以让它工作,但我不想这样做,因为它对于完全相同的最终结果要麻烦得多。例如
type IType1 = 
    abstract member doSomething : int -> int

type func1 () =
    interface IType1 with
        member this.doSomething (i: int) = i * i

允许我设置一个模拟
let mT1= Mock.With (fun (x : IType1) -> <@ x.doSomething(any()) --> 5 @>)

但我真的不想这样做。

2 - 如果我只是使用
type IType1 = int -> int
let mT1 = Mock.Of<IType1>()

然后我得到一个有效的值,但是如果我尝试以任何方式配置它
let mT1= Mock<IType1>.With (fun x -> <@ x(any()) --> 5 @>)

或者
let mT1= Mock<IType1>.With (fun x -> <@ any() --> 5@>)

然后我得到一个异常(exception)
System.NotSupportedException : Expected standard function application: Call 

或者
System.NotSupportedException : Expected standard function application: ValueWithName 

我希望我只是对语法很愚蠢,并且可以做我想做的事。我已经尝试了所有我能想到的变体,包括 .Setup(conditions).Create() 的变体,但我在源代码中找不到任何示例。

我显然可以很容易地制作自己的模拟像
let mT1 (i : int) : int = 5

因为任何适合该 int -> int 签名的东西都是有效的,但是如果我想检查该函数是否传递了某个值,我必须放入一个日志记录步骤等。如果有就好了Foq 做了一些繁重的工作。

编辑
我刚刚注意到根 Mock 对象的签名中有“需要引用类型”(即 Mock<'TAbstract(requires reference type)> )——这是否意味着我没有机会模拟值?如果我不配置模拟,它如何管理它?

最佳答案

你不必 mock 。如果你的依赖只是函数类型,你可以只提供函数:

let mT1= fun x -> 5

对象模拟的整个概念是(必须)由面向对象的人发明的,以弥补对象组合不好(或根本不组合)的事实。当您的整个系统正常运行时,您可以在现场创建功能。无需 mock 。

如果你真的很想使用 Foq 的工具,比如日志记录和验证(我敦促你重新考虑:你的测试会更容易和更有弹性),你总是可以让自己成为一个对象,作为你的代理主机职能:
type ISurrogate<'t, 'r> =
    abstract member f: 't -> 'r

// Setup
let mT1 = Mock.Create<ISurrogate<int, int>>()
mT1.Setup(...)...

let mT2 = Mock.Create<ISurrogate<int, string>>()
mT2.Setup...

higherFunc mT1.f mT2.f 42

mT1.Received(1).Call( ... ) // Verification

通过这种方式,丑陋仅限于您的测试,并且不会使您的生产代码复杂化。

显然,这仅适用于单参数函数。对于具有多个 curried 参数的函数,您必须对参数进行元组化并将调用包装在注入(inject)站点的 lambda 中:
// Setup
let mT1 = Mock.Create<ISurrogate<int * int, int>>()

higherFunc (fun x y -> mT1.f(x, y)) ...

如果您发现这种情况经常发生,您可以打包 lambda 创建以供重用:
let inject (s: ISurrogate<_,_>) x y = s.f (x,y)

higherFunc (inject mT1) ...

关于.net - 将 Foq 与 F# 函数类型一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48842609/

相关文章:

c# - 对称加密加密很多小块

java - 如何使用 Mockito/Powermockito 在内部类中对逻辑进行单元测试

java - 在 Java 中设置 Saxon 的当前日期时间

unit-testing - 了解如何使用 Mockito doReturn/when 对返回类型为 void 的方法进行 JUnit 测试

f# - 与计算表达式的相互递归

python - FSharp 运行我的算法比 Python 慢

c# - 字符串格式化程序可以用变量参数化吗?

.net - 为什么可空类型会有这样的行为

c# - 关于字符串实习和替代方案

exception - 对于 vs2012 中使用 mstest 用 F# 编写的单元测试,如何断言引发异常?