[C#]Fixing Return Values and Tracking Property Changes with Moq

目次

Overview

This article explains how to define the behavior of interface properties using the Moq library for .NET unit testing. We will cover two patterns: “read-only” behavior that returns a fixed value, and “auto-implemented” behavior that tracks and remembers assigned values. You can choose the best method depending on whether your test code only reads the property or needs to update and reuse its value.

Specifications (Input/Output)

  • Input:
    • The interface definition you want to mock.
    • SetupGet: A fixed value to return when the property is accessed.
    • SetupProperty: An optional initial value for a property that tracks changes.
  • Output:
    • With SetupGet: The property always returns the specified fixed value. Even if a set operation is performed, the value remains unchanged.
    • With SetupProperty: The property behaves like a standard class property. It retains values assigned via set and returns them via get.
  • Prerequisite: The NuGet package Moq must be installed.

Basic Usage

This is the basic form used to ensure a specific property always returns a fixed value when accessed.

// Create the mock
var mock = new Mock<IUserSettings>();

// Configure the Theme property to always return "Dark"
mock.SetupGet(x => x.Theme).Returns("Dark");

// Retrieval
var value = mock.Object.Theme; // "Dark"

Full Code

The following console application compares the “fixed value” approach with the “change tracking” approach.

using System;
using Moq;

// Requires Moq package installation via NuGet:
// dotnet add package Moq

namespace MoqPropertyExample
{
    // Interface to be mocked
    public interface IServerConfiguration
    {
        string HostName { get; set; }
        int TimeoutSeconds { get; set; }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("--- 1. Fixed Return Value (SetupGet) ---");
            RunSetupGetDemo();

            Console.WriteLine("\n--- 2. Tracking Value Changes (SetupProperty) ---");
            RunSetupPropertyDemo();
        }

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

            // When the HostName property is accessed, it always returns "db-server-01"
            // Note: Setting a value will be ignored; the Get result will not change.
            mock.SetupGet(m => m.HostName).Returns("db-server-01");

            var config = mock.Object;

            Console.WriteLine($"Initial Value: {config.HostName}");

            // Attempting to change the value will not be reflected because it is fixed by SetupGet.
            config.HostName = "web-server-99";
            Console.WriteLine($"After Change: {config.HostName}"); 
        }

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

            // Make the TimeoutSeconds property act like a normal property (tracking values)
            // You can set an initial value in the second argument (30 in this case)
            mock.SetupProperty(m => m.TimeoutSeconds, 30);

            var config = mock.Object;

            Console.WriteLine($"Initial Value: {config.TimeoutSeconds}");

            // Changing the value updates the internal state of the mock object
            config.TimeoutSeconds = 60;
            Console.WriteLine($"After Change: {config.TimeoutSeconds}");

            // Change it again
            config.TimeoutSeconds = 120;
            Console.WriteLine($"Second Change: {config.TimeoutSeconds}");
        }
    }
}

Output Example

--- 1. Fixed Return Value (SetupGet) ---
Initial Value: db-server-01
After Change: db-server-01

--- 2. Tracking Value Changes (SetupProperty) ---
Initial Value: 30
After Change: 60
Second Change: 120

Customization Points

  • Dynamic Return Values: Using Returns(() => SomeFunction()) instead of Returns("FixedValue") allows you to calculate and return a value every time the property is accessed.
  • Initial Value Settings: The second argument of SetupProperty is optional. If omitted, the property is initialized with the default value of its type (such as null or 0).

Points of Caution

  • Priority of SetupGet: If you set a value to a property configured with SetupGet, no exception is thrown. However, the next get call will still return the value defined in SetupGet. This behavior can be confusing if your test assumes the value will update.
  • Hierarchical Properties: When mocking deep hierarchies like mock.Setup(m => m.Child.Property), ensure that the intermediate Child property does not return null by properly setting up the parent mock.
  • Interface Constraints: You cannot use SetupProperty on properties that do not have a set accessor in the interface definition.

Application

Automatically Implementing All Properties

If an interface has many properties and you want all of them to track values, writing SetupProperty for each one is tedious. You can use SetupAllProperties to configure them all at once.

var mock = new Mock<IServerConfiguration>();

// Enable all properties in the interface to track values
mock.SetupAllProperties();

// You can still set specific initial values manually
mock.Object.HostName = "localhost";
mock.Object.TimeoutSeconds = 500;

This method is very effective when you need a mock that acts as a simple Data Transfer Object (DTO).

Summary

For bulk configurations, SetupAllProperties() is a convenient way to make all properties auto-implemented. If you only need a property to return a constant value for reading purposes, SetupGet(m => m.Prop).Returns(value) is the appropriate choice. When you need an object that maintains its state throughout a test as values are updated, use SetupProperty(m => m.Prop, initialValue).

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

この記事を書いた人

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

目次