[C#]Defining Argument Conditions and Dynamic Return Values with Moq

目次

Overview

This article explains how to define method behavior when using the Moq library for .NET. We will cover three patterns: “Exact Match” which returns a value only for a specific argument, “Conditional Match” which responds to arguments meeting certain criteria, and “Dynamic Return Values” which calculate results based on the provided arguments.

Specifications (Input/Output)

  • Input:
    • The interface to be mocked (e.g., ticket pricing).
    • Setup: The definition of the method call.
    • Returns: The definition of the returned value.
  • Output Patterns:
    • Pattern 1 (Exact Match): Returns 0 only if the argument is 0. Returns default values for anything else.
    • Pattern 2 (Conditional Match): Returns 1000 if the argument is 65 or greater.
    • Pattern 3 (Dynamic Calculation): Calculates and returns child or standard rates based on the age argument.
  • Prerequisite: The NuGet package Moq is required.

Basic Usage

This is the most basic configuration that returns a fixed value only when a specific argument (in this case, 0) is passed.

var mock = new Mock<ITicketPricing>();

// Returns 0 only when the argument is 0 (Exact Match)
mock.Setup(x => x.GetPrice(0)).Returns(0);

// Execution
int price = mock.Object.GetPrice(0); // Result: 0

Full Code

The following implementation demonstrates the “Exact Match,” “Conditional Match,” and “Dynamic Logic” patterns within a ticket pricing scenario.

using System;
using Moq;

// Required NuGet package:
// dotnet add package Moq

namespace MoqMethodExample
{
    // Interface to be tested: Ticket pricing
    public interface ITicketPricing
    {
        int GetPrice(int age);
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("--- 1. Fixed Value for Specific Argument (Exact Match) ---");
            RunExactMatchDemo();

            Console.WriteLine("\n--- 2. Value for Matching Conditions (It.Is) ---");
            RunConditionalMatchDemo();

            Console.WriteLine("\n--- 3. Calculating Return Values from Arguments (Dynamic Returns) ---");
            RunDynamicReturnDemo();
        }

        static void RunExactMatchDemo()
        {
            var mock = new Mock<ITicketPricing>();

            // Returns 0 only when age is "0"
            // If called with other values, it returns the default for int (0) 
            // unless Strict mode is used.
            mock.Setup(m => m.GetPrice(0)).Returns(0);
            
            // Setting another specific value
            mock.Setup(m => m.GetPrice(20)).Returns(1800);

            var calculator = mock.Object;

            Console.WriteLine($"Age 0: {calculator.GetPrice(0)} yen");   // 0
            Console.WriteLine($"Age 20: {calculator.GetPrice(20)} yen"); // 1800
            Console.WriteLine($"Age 99: {calculator.GetPrice(99)} yen"); // No setup -> 0
        }

        static void RunConditionalMatchDemo()
        {
            var mock = new Mock<ITicketPricing>();

            // Returns 1000 if the argument is "65 or greater"
            mock.Setup(m => m.GetPrice(It.Is<int>(age => age >= 65)))
                .Returns(1000);

            var calculator = mock.Object;

            Console.WriteLine($"Age 65: {calculator.GetPrice(65)} yen"); // 1000
            Console.WriteLine($"Age 80: {calculator.GetPrice(80)} yen"); // 1000
            Console.WriteLine($"Age 20: {calculator.GetPrice(20)} yen"); // No setup -> 0
        }

        static void RunDynamicReturnDemo()
        {
            var mock = new Mock<ITicketPricing>();

            // Calculates and returns a value using a lambda expression regardless of the input (It.IsAny)
            mock.Setup(m => m.GetPrice(It.IsAny<int>()))
                .Returns((int age) =>
                {
                    if (age < 0) throw new ArgumentException("Invalid age provided");
                    if (age <= 12) return 900;  // Child rate
                    if (age >= 65) return 1200; // Senior rate
                    return 1900;                // Regular rate
                });

            var calculator = mock.Object;

            try
            {
                Console.WriteLine($"Age 10: {calculator.GetPrice(10)} yen"); // 900
                Console.WriteLine($"Age 30: {calculator.GetPrice(30)} yen"); // 1900
                Console.WriteLine($"Age 70: {calculator.GetPrice(70)} yen"); // 1200
                // Check exception
                calculator.GetPrice(-5);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}

Output Example

--- 1. Fixed Value for Specific Argument (Exact Match) ---
Age 0: 0 yen
Age 20: 1800 yen
Age 99: 0 yen

--- 2. Value for Matching Conditions (It.Is) ---
Age 65: 1000 yen
Age 80: 1000 yen
Age 20: 0 yen

--- 3. Calculating Return Values from Arguments (Dynamic Returns) ---
Age 10: 900 yen
Age 30: 1900 yen
Age 70: 1200 yen
Error: Invalid age provided

Customization Points

  • Mocking Async Methods: If a method returns Task<int>, use .ReturnsAsync(1000) or .ReturnsAsync((int x) => x * 10).
  • Throwing Exceptions: Use .Throws<Exception>() or .Throws(new Exception("...")) instead of .Returns to test error-handling conditions.
  • Callbacks: Use .Callback((int x) => ...) if you want to change the state of a variable when a method is called in addition to returning a value.

Points of Caution

  • It.IsAny and Type Specification: It.IsAny<T>() matches every value of that type. If you mix it with It.Is<T>(condition), be careful with the order of your Setup calls. Moq generally follows a “last definition wins” rule, but it is safer to keep conditions distinct.
  • Strict Mode Behavior: By default (Loose mode), Moq returns default values for methods that are not set up. If you use MockBehavior.Strict, an exception will be thrown instead.
  • Logic Complexity: Avoid writing overly complex if/else logic inside Returns. Mocks should be simple tools that provide the behavior necessary for the test case; keep the logic to a minimum.

Application

Returning Different Values Each Call (Sequences)

For testing scenarios like “succeed on the first call and fail on the second,” use SetupSequence.

mock.SetupSequence(m => m.GetPrice(It.IsAny<int>()))
    .Returns(1000)  // First call
    .Returns(500)   // Second call
    .Throws(new Exception("System Error")); // Third call

Summary

The Setup method in Moq provides great flexibility by combining argument conditions via It.Is with dynamic return value generation using lambda expressions. You should choose “Exact Match” for simple constant values, “Conditional Match” for range-based requirements, and “Dynamic Calculation” when the output must be processed based on the input values.

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

この記事を書いた人

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

目次