C# で関数を使って Mock のようなことをする

タイムラインの技術イケメンが、「Mock を使うべき理由がわからない」という話をポストしていて、その解法として、そもそも設計が悪いという話と、高階関数のような方法で コンストラクタに関数を渡せばいいやんという話をいただいたのですが、試したことがなかったのでやってみました。

# コード

```csharp
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;

namespace FunctionalTesting
{

    public class Target
    {
        private Func<string, string> bar;
        private Func<string> foo;
        private Action<string> some;
        public Target ()
        {
            bar = (a) =>
            {
                Console.WriteLine($"GetBar: {a}");
                return $"Remote Call: GetBar: {a}";
            };
            foo = () =>
            {
                Console.WriteLine($"Remote Call: GetFoo!");
                return "GetFoo!";
            };
            some = (a) => {
                Console.WriteLine($"Remote Call: Some {a}");
            };
        }

        public Target(Func<string, string> bar, Func<string> foo, Action<string> some)
        {
            this.bar = bar;
            this.foo = foo;
            this.some = some;
        }

        public async Task<string> GetBarAsync(string param)
        {
            return bar(param);
        }
        public async Task<string> GetFooAsync()
        {
            return foo();
        }

        public async Task SetSomeAsync(string param)
        {
            some(param);
        }

      
    }

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public async Task TestFunctionAsync()
        {
            var target = new Target();
            await target.GetFooAsync();
            await target.GetBarAsync("aaa");
            await target.SetSomeAsync("bbb");

            string someResult = "";
            target = new Target(
                (a) =>
                {
                    Console.WriteLine("Mocked! Bar");
                    return $"Mocked Bar: {a}";
                },
                () =>
                {
                    Console.WriteLine("Mocked! Foo");
                    return $"Mocked Foo:";
                },
                (a) =>
                {
                    Console.WriteLine("Mocked! Some");
                    someResult = $"Some Parameter: {a}";
                });

            Assert.AreEqual("Mocked Bar: aaa",await target.GetBarAsync("aaa"));
            Assert.AreEqual("Mocked Foo:", await target.GetFooAsync());
            await target.SetSomeAsync("ccc");
            Assert.AreEqual("Some Parameter: ccc", someResult);


        }
    }
}

```

無事うまくいきました。

![FUNC.png](https://qiita-image-store.s3.amazonaws.com/0/3470/0d208e75-d8ae-19d0-f5b2-acf020a68d2e.png)

# コードの解説

関数を高階関数として渡して処理するのはとても簡単でした。

## 基本的な関数

関数オブジェクトは、`Func` キーワードで定義できます。例えば `Func<string, string>` は、引数１、戻り値１の関数です。

```csharp
    :
private Func<string, string> bar;
    :
```

```csharp
        public async Task<string> GetBarAsync(string param)
        {
            return bar(param);
        }
```

直接コーデイングをせず、bar という関数オブジェクトを実行するようにします。実態はデフォルトコンストラクタで渡しています。

```csharp
            bar = (a) =>
            {
                Console.WriteLine($"GetBar: {a}");
                return $"Remote Call: GetBar: {a}";
            };
```

ちなみに、関数のパラメータでも関数を渡せます。当初は、関数のオプション（デフォルト値月の引数）で実装しようとしましたが、const でないと認められないようなので、このような実装にしてみました。

同じように引数の無く、戻り値１の関数も、`private Func<string> foo;` といったノリで定義できます。

## 戻り値の無い関数

じゃあ、戻り値が無いのはどうするのだろう？と思いますが、`Func` では書けません。代わりに、`Action` を使います。

```csharp
        private Action<string> some;
```

使い方は `Func` と全く同じなので、コードをご覧ください。

# テストの実行

さて、Mock に該当するテストを書いてみましょう。 テストのところで、外部呼出しなどを次のような感じで違う関数に入れ替えてあげればシンプルに似たことができます。

```csharp
            target = new Target(
                (a) =>
                {
                    Console.WriteLine("Mocked! Bar");
                    return $"Mocked Bar: {a}";
                },
                () =>
                {
                    Console.WriteLine("Mocked! Foo");
                    return $"Mocked Foo:";
                },
                (a) =>
                {
                    Console.WriteLine("Mocked! Some");
                    someResult = $"Some Parameter: {a}";
                });
```

## 引数にわたってきた値の調査

Moq では、関数から戻ってきた値を Assert する以外に、Mock に適切に値が渡ってきたかをテストしたいときがあります。その時もとても簡単で、こんな感じでいいでしょう。

```csharp
var someResult = "";
    :
            target = new Target(
                       :
                (a) =>
                {
                    Console.WriteLine("Mocked! Some");
                    someResult = $"Some Parameter: {a}";
                });

            await target.SetSomeAsync("ccc");
            Assert.AreEqual("Some Parameter: ccc", someResult);
```

確かに Mock ライブラリが無くても似たようなことがシンプルにできますね！