【C#】Moqでプロパティの戻り値固定と値の保持(追跡)を実装する方法

目次

概要

.NETの単体テストにおけるモックライブラリ「Moq」を使用して、インターフェースのプロパティの振る舞いを定義する方法です。 単に値を返すだけの「読み取り専用」のような振る舞いと、値の代入を記憶して保持する「自動実装プロパティ」のような振る舞いの2パターンを解説します。 テスト対象のコードがプロパティの値を読み取るだけなのか、更新して再利用するのかによって使い分けます。

仕様(入出力)

  • 入力
    • モック化したいインターフェース定義
    • SetupGet: プロパティが呼ばれた際に返す固定値
    • SetupProperty: プロパティの初期値(任意)
  • 出力
    • SetupGetを設定した場合: 何度呼び出しても設定した固定値を返す。setを行っても値は変わらない(または設定によりエラー)。
    • SetupPropertyを設定した場合: 通常のクラスのプロパティのように、setで代入された値を保持し、getでその値を返す。
  • 前提
    • NuGetパッケージ Moq がインストールされていること。

基本の使い方

特定のプロパティが参照されたときに、常に決まった値を返すように設定する基本形です。

// モックの作成
var mock = new Mock<IUserSettings>();

// Themeプロパティが呼ばれたら常に "Dark" を返すように設定
mock.SetupGet(x => x.Theme).Returns("Dark");

// 取得
var value = mock.Object.Theme; // "Dark"

コード全文

「固定値を返す設定」と「値を保持・更新できる設定」の2種類を比較できる完全なコンソールアプリケーションのコードです。

using System;
using Moq;

// 実行には NuGet で Moq パッケージのインストールが必要です
// dotnet add package Moq

namespace MoqPropertyExample
{
    // モック対象のインターフェース
    public interface IServerConfiguration
    {
        string HostName { get; set; }
        int TimeoutSeconds { get; set; }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("--- 1. 固定値を返す設定 (SetupGet) ---");
            RunSetupGetDemo();

            Console.WriteLine("\n--- 2. 値の変更を追跡する設定 (SetupProperty) ---");
            RunSetupPropertyDemo();
        }

        static void RunSetupGetDemo()
        {
            var mock = new Mock<IServerConfiguration>();

            // HostNameプロパティが呼ばれたら、常に "db-server-01" を返す
            // ※ 値をセットしても無視され、Getの結果は変わりません
            mock.SetupGet(m => m.HostName).Returns("db-server-01");

            var config = mock.Object;

            Console.WriteLine($"初期値: {config.HostName}");

            // 値を変更しようとしても、SetupGetで固定されているため反映されない(または上書きされない)
            config.HostName = "web-server-99";
            Console.WriteLine($"変更後: {config.HostName}"); 
        }

        static void RunSetupPropertyDemo()
        {
            var mock = new Mock<IServerConfiguration>();

            // TimeoutSecondsプロパティを、通常のプロパティとして動作させる(値の保持)
            // 第2引数で初期値を設定可能(ここでは 30)
            mock.SetupProperty(m => m.TimeoutSeconds, 30);

            var config = mock.Object;

            Console.WriteLine($"初期値: {config.TimeoutSeconds}");

            // 値を変更すると、モックオブジェクト内部でその値が保持される
            config.TimeoutSeconds = 60;
            Console.WriteLine($"変更後: {config.TimeoutSeconds}");

            // さらに変更
            config.TimeoutSeconds = 120;
            Console.WriteLine($"再変更: {config.TimeoutSeconds}");
        }
    }
}

出力例

--- 1. 固定値を返す設定 (SetupGet) ---
初期値: db-server-01
変更後: db-server-01

--- 2. 値の変更を追跡する設定 (SetupProperty) ---
初期値: 30
変更後: 60
再変更: 120

カスタムポイント

  • 戻り値の動的生成
    • Returns("FixedValue") の代わりに Returns(() => SomeFunction()) を使用すると、アクセスされるたびに値を計算して返すことができます。
  • 初期値の設定
    • SetupProperty の第2引数は省略可能です。省略した場合、型に応じたデフォルト値(null0)で初期化されます。

注意点

  1. SetupGetの優先度
    • SetupGet で設定したプロパティに対して値を set しても、例外は発生しませんが、次に get した際には SetupGet で定義した値が返されます。「値が変わらない」という挙動になるため、テスト対象が値の更新を前提としている場合は混乱の元になります。
  2. 階層構造のプロパティ
    • mock.Setup(m => m.Child.Property) のように階層深く設定する場合、中間の Child プロパティが null を返さないように、親のモック設定も適切に行う必要があります(または DefaultValue プロパティの設定でカバーする)。
  3. インターフェース定義の制約
    • 当然ながら、インターフェースに set アクセサがないプロパティに対して SetupProperty は使用できません。

応用

すべてのプロパティを一括で自動実装化する

プロパティが多数あり、すべてに対して値を保持させたい場合、個別に SetupProperty を書くのは手間です。SetupAllProperties を使うと一括設定できます。

var mock = new Mock<IServerConfiguration>();

// インターフェース内のすべてのプロパティを、値を保持可能な状態にする
mock.SetupAllProperties();

// 個別に初期値を設定したい場合のみ、後から設定可能
mock.Object.HostName = "localhost";
mock.Object.TimeoutSeconds = 500;

この方法は、単なるデータコンテナ(DTO)として振る舞うモックが必要な場合に非常に有効です。

まとめ

一括設定: 全プロパティを自動実装化したい場合は SetupAllProperties() が便利です。

固定値を返したい場合: SetupGet(m => m.Prop).Returns(値) を使用します。参照のみの用途に適しています。

値を更新・保持したい場合: SetupProperty(m => m.Prop, 初期値) を使用します。テスト中に状態が変わるオブジェクトに適しています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次