rksoftware

Visual Studio とか C# とかが好きです

C# で Moq でモックを使ったテストをする

今回は C# で Moq でモックを使ったテストをする最低限の要素をメモします。

■ テストプロジェクトの作成

コマンドで作成できます。
今回は NUnit で進めていこうと思うので、NUnit プロジェクトを作成します。プロジェクト名は SampleTestProject としてみます。

dotnet new nunit -n SampleTestProject

もし、MSTest が良ければこうです。

dotnet new mstest -n SampleTestProject

Visual Studio でも作れます。
f:id:rksoftware:20211202003513j:plain

コマンドと Visual Studio の違い

コマンドで作ったプロジェクトと Visual Studio で作ったプロジェクトでは最初に作られているテストクラスのコードに大きな違いがあります。
次の前者が dotnet コマンド、後者が Visual Studio です。なんと! Visual Studio ではファイルスコープの名前空間が使われていません。これはほんの少しの手間ですが直すのに手間がかかります。素直に dotnet コマンドで作るのがよさそうです。

using NUnit.Framework;

namespace SampleTestProject;

public class Tests
{
    [SetUp]
    public void Setup()
    {
    }

    [Test]
    public void Test1()
    {
        Assert.Pass();
    }
}
using NUnit.Framework;

namespace SampleTestProject
{
    public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Test1()
        {
            Assert.Pass();
        }
    }
}

■ Moq のインストール

テスト用モックライブラリの Moq は NuGet からインストールします。

cd SampleTestProject
dotnet add package Moq

Visual Studio の GUI でもインストールできます。
f:id:rksoftware:20211202005234j:plain

■ テストコード例

== モックを作るインターフェイス

interface ISample
{
    // モックを作るメソッド
    int SampleMethod(int value);
}

== テスト対象のクラス。このクラス単体のテストを行うためには、依存している ISample のモックが必要

class TestableClass
{
    public int TestableMethod(ISample sample)
    {
        // 依存している ISample のメソッドを 2 回呼んでいる。2 回目の結果を return している
        var value = sample.SampleMethod(0);
        return sample.SampleMethod(value);
    }
}

== テストクラス。この記事の本体

using NUnit.Framework;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

namespace SampleTestProject;

public class Tests
{
    [SetUp]
    public void Setup()
    {
    }

    [Test]
    public void Test1()
    {
        // モックを準備
        Moq.Mock<ISample> moq = new Moq.Mock<ISample>();
        // モックメソッド。0 という引数で呼ばれたときは 10 を return する。10 という引数では 11 を return
        moq.Setup(x => x.SampleMethod(0)).Returns(10);
        moq.Setup(x => x.SampleMethod(10)).Returns(11);
        // モックのオブジェクトを取得
        ISample mock = moq.Object;

        // テストを実行
        TestableClass testableClass = new();
        var value = testableClass.TestableMethod(mock);

        // テスト結果検証
        Assert.AreEqual(value, 11);
        // モックメソッドが 0 という引数では 1 回呼ばれていれば OK
        moq.Verify(x => x.SampleMethod(0), Moq.Times.Once);
        // モックメソッドが 10 という引数では 1 回呼ばれていれば OK
        moq.Verify(x => x.SampleMethod(10), Moq.Times.Once);
    }
}

■ InternalsVisibleTo

テストを行う際はだいたいはプロダクトのプロジェクトで Internals になっているクラスをテストする機会があると思います。その場合、プロダクトのプロジェクトで InternalsVisibleTo の設定をします。
プロダクトのプロジェクトに InternalVisibleTos.cs といった名前でファイルを追加します。

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("{テストプロジェクト名}")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

もし InternalsVisibleTo の設定がないと

エラー CS0122 'XXXXXXXX' はアクセスできない保護レベルになっています

"Can not create proxy for type XXXXXXXXXX because it is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo(\"DynamicProxyGenAssembly2\")] attribute, because assembly XXXXXXXXXX is not strong-named."} Castle.DynamicProxy.Generators.GeneratorException

といったエラーが出ます。

簡単ですね。