Using Foq with F# function types

回眸只為那壹抹淺笑 提交于 2020-07-08 11:52:48

问题


I am using F# type definitions to prevent hard dependencies between my functions, for example

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

output x : "4"

Obviously this is quite contrived and simple but imagine the dependencies are a parser and a sorter or whatever. Smaller grains of functionality that I am composing.

I am trying to use Foq to help with my unit test fixtures. This is my first week using F# properly and I am having a hard time trying to work out how to configure a mock of these types.

Two things are worth mentioning:

1 - I can make it work if I use abstract classes, but I don't want to do this as it's so much more hassle for exactly the same end result. For example

type IType1 = 
    abstract member doSomething : int -> int

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

allows me to set up a mock like

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

but I really don't want to have to do this.

2 - If I just use

type IType1 = int -> int
let mT1 = Mock.Of<IType1>()

then I get back a valid value, but if I try to configure it in any way like

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

or

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

then I get an exception of

System.NotSupportedException : Expected standard function application: Call 

or

System.NotSupportedException : Expected standard function application: ValueWithName 

I am hoping that I'm just being stupid with the syntax and that it is possible to do what I want. I have tried every variation I can think of, including variations of .Setup(conditions).Create(), and I can't find any examples in the source.

I can obviously easily make my own mocks like

let mT1 (i : int) : int = 5

as anything which fits that int -> int signature will be valid, but then if I want to check that the function was passed a certain value for i I have to put in a logging step etc etc.. It would just be nice to have Foq to do some of the heavy lifting.

Edit I just noticed that the root Mock object has 'requires reference type' in its signature ( i.e. Mock<'TAbstract(requires reference type)> ) - does that mean I have no chance of mocking values? How come it manages it if I don't configure the mock?


回答1:


You don't have to mock. If your dependencies are just function types, you can just provide functions:

let mT1= fun x -> 5

The whole concept of object mocking was (had to be) invented by the object-oriented people to compensate for the fact that objects don't compose well (or at all). When your whole system is functional, you can just create functions on the spot. No mocking necessary.

If you're really hung up on using Foq's facilities like logging and verifying (which I urge you to reconsider: your testing would come out easier and more resilient), you can always make yourself an object that would act as a surrogate host for your functions:

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

This way, the ugliness is confined to your tests and does not complicate your production code.

Obviously, this will only work for single-argument functions. For functions with multiple curried arguments, you'll have to tuple the arguments and wrap the call in a lambda at injection site:

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

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

If you find this happening often, you can pack the lambda creation for reuse:

let inject (s: ISurrogate<_,_>) x y = s.f (x,y)

higherFunc (inject mT1) ...


来源:https://stackoverflow.com/questions/48842609/using-foq-with-f-function-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!