他人様の知恵を拝借して、F#にモックフレームワークを導入しましたよー、というお話

私はC#ではMoqというモックフレームワークを愛用しているのですが、このMoq、F#においては思うように動作してくれなかったのです。

環境

を利用しております。その他の環境では確認しておりませんのでご了承下さい。

Moq

C#では使い勝手の良いMoqを何とか、F#でも使おうとあーだこーだソースをこねくり回していましたが、結局挫折しました。
F#のラムダ式LINQ expressionsに変換する必要があるのですが、その変換にどうやら無理があるようです。

ググったら以下のようなエントリを見つけました。

ふむふむ。どちらもMoqはF#では今ひとつだよと書かれてますね。
それで、F#でモックフレームワークを使うならRhino Mocksが良いとも書かれてます。

Rhino Mocksとはなんぞや?

Rhino Mocks

先のブログによりますと、"Rhino.Mocks is the most popular .NET mock framework."だそうです。
私、今まで知りませんでした。モックフレームワークって多彩なんですね。
という訳でRhino Mocksを試してみました。

以下のインタフェースに対してモックを作ってみたいと思います。

    type IGreet = interface
      abstract Greet : string -> string 
    end

例によって、Rhino MocksのDLLを参照に追加して、テストコードは以下のようになります。

open System
open NUnit.Framework
open Rhino.Mocks

[<TestFixture>]
type MyTest() =

  [<Test>]
  member x.RhinoMockSample () =

    let mock1 = MockRepository.GenerateMock<IGreet>()
    RhinoMocksExtensions.Stub<IGreet, string>(mock1, fun m -> m.Greet "hello").Return("Hello")
      |> ignore
    Assert.AreEqual("Hello", mock1.Greet "hello")
    RhinoMocksExtensions.VerifyAllExpectations(mock1)

    let mock2 = MockRepository.GenerateMock<IGreet>()
    RhinoMocksExtensions.Stub<IGreet, string>(mock2, fun m -> m.Greet "hello2").Return("HelloHello")
      |> ignore
    Assert.AreEqual("HelloHello", mock2.Greet "hello2")
    RhinoMocksExtensions.VerifyAllExpectations(mock2)

    let mock3 = MockRepository.GenerateMock<IGreet>()
    RhinoMocksExtensions.Stub<IGreet, string>(mock3, fun m -> m.Greet "hello3").Return("HelloHelloHello")
      |> ignore
    Assert.AreEqual(null, mock3.Greet "hell")
    RhinoMocksExtensions.VerifyAllExpectations(mock3)

ビルドし、テストしたところ、無事にテストにパスしました。

さらに、先のブログにこんな便利なラッパーがあったのでご紹介します。

(*
original->
http://bloggingabout.net/blogs/vagif/archive/2010/08/04/mock-framework-challenges-in-f.aspx
*)
type mock<'T when 'T : not struct>() =
  let instance = MockRepository.GenerateMock<'T>()

  member this.Object = instance

module Mock = 
  let arrange<'T, 'R when 'T : not struct>(f : ('T -> 'R))(r : 'R)(m : mock<'T>) =
    RhinoMocksExtensions.Stub<'T, 'R>(m.Object, Function<'T, 'R>(f)).Return(r)

これを使うとRhino Mocksがさらに簡単に書けるようになります。

  [<Test>]
  member x.MockTest2 () =
    let mock1 = mock<IGreet>()
    mock1 |> Mock.arrange (fun m -> m.Greet "hello") "HELLO" |> ignore
    let greet = mock1.Object

    Assert.AreEqual("HELLO", greet.Greet "hello")
    Assert.AreEqual(null, greet.Greet "hell")

    RhinoMocksExtensions.VerifyAllExpectations(greet)

とても綺麗ですね。

以下は、少し自分好みに改良してみたつもりです。

type mock<'T when 'T : not struct>() =
  let instance = MockRepository.GenerateMock<'T>()

  member this.Object = instance

  member this.Setup<'R when 'T : not struct>(f : ('T -> 'R))(r : 'R) =
    RhinoMocksExtensions.Stub<'T, 'R>(this.Object, Function<'T, 'R>(f)).Return(r)

  member this.Setup2<'R when 'T : not struct>(f : ('T -> 'R)) =
    RhinoMocksExtensions.Stub<'T, 'R>(this.Object, Function<'T, 'R>(f))

文脈を解り易くしたいと思い、Setup2はReturnを省略してみました。
テストコードは以下のようになります。

  [<Test>]
  member x.MockTest2 () =
    let mock1 = mock<IGreet>()    
    mock1.Setup (fun m -> m.Greet "hello") "HELLO" |> ignore
    (mock1.Setup2 (fun m -> m.Greet "日本語")).Return "こんにちは" |> ignore

    let greet = mock1.Object

    Assert.AreEqual("HELLO", greet.Greet "hello")
    Assert.AreEqual("こんにちは", greet.Greet "日本語")
    Assert.AreEqual(null, greet.Greet "hell")

    RhinoMocksExtensions.VerifyAllExpectations(greet)

…。
Setup2は余計でしたね。

こんな感じで、Rhino MocksはF#ではかなり使える存在になりそうです。