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
0only if the argument is0. Returns default values for anything else. - Pattern 2 (Conditional Match): Returns
1000if the argument is65or greater. - Pattern 3 (Dynamic Calculation): Calculates and returns child or standard rates based on the age argument.
- Pattern 1 (Exact Match): Returns
- Prerequisite: The NuGet package
Moqis 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.Returnsto 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 withIt.Is<T>(condition), be careful with the order of yourSetupcalls. 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/elselogic insideReturns. 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.
