devlooped/moq · error · NotSupportedException

Ref expression must evaluate to a constant value.

Error message

Ref expression must evaluate to a constant value.

What it means

Moq's MatcherFactory throws this NotSupportedException when a `Ref<T>` matcher argument's expression cannot be partially evaluated down to a constant value. `Ref` matches arguments by reference equality, so Moq must capture a constant value at setup time; if the expression is not constant (e.g. a variable or computed value), it cannot be used as a reference sentinel.

Solutions

  1. Use `new Ref<object>(value)` or pass a constant/readonly field value in the setup expression
  2. Assign the value to a `const` or capture it via a typed Ref sentinel so PartialEval yields a ConstantExpression
  3. If the value varies, use `It.Ref<object>.IsAny` instead of a fixed reference match

Example fix

// before
object value = GetValue();
mock.Setup(m => m.DoWork(ref value)).Returns(true);
// after
mock.Setup(m => m.DoWork(ref It.Ref<object>.IsAny)).Returns(true);
Defensive patterns

Strategy: validation

Validate before calling

var captured = value; // ensure the ref arg is a constant/local capture
if (!(Expression.Constant(captured) is ConstantExpression)) throw new InvalidOperationException("Ref argument must be constant");

Type guard

static bool IsConstantRef(object? v) => v is string or int or long or double or bool or null;

Try / catch

try { mock.Setup(m => m.DoWork(ref arg))... } catch (NotSupportedException ex) when (ex.Message.Contains("Ref expression")) { /* use It.Ref<T>.IsAny */ }

Prevention

When it happens

Trigger: Calling It.Is or a setup with `Ref` semantics (via MatcherFactory.CreateMatcher) where the argument expression passed to the ref parameter is not a constant, e.g. `mock.Setup(m => m.Method(ref someLocalVar))` with a non-const local.

Common situations: Developers passing a mutable local variable or computed value to a ref parameter in a setup, expecting Moq to capture it; Moq 4.x requires a constant expression for ref matchers.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of devlooped/moq@89a5be629c (2026-09-16). Data as JSON: /api/errors/3124ee1a220f5041. Report an issue: GitHub.

Appendix: source

Thrown at src/Moq/MatcherFactory.cs:72

                        {
                            var memberDeclaringType = member.DeclaringType!;
                            if (memberDeclaringType.IsGenericType)
                            {
                                var memberDeclaringTypeDefinition = memberDeclaringType.GetGenericTypeDefinition();
                                if (memberDeclaringTypeDefinition == typeof(It.Ref<>))
                                {
                                    return new Pair<IMatcher, Expression>(AnyMatcher.Instance, argument);
                                }
                            }
                        }
                    }

                    if (argument.PartialEval() is ConstantExpression constant)
                    {
                        return new Pair<IMatcher, Expression>(new RefMatcher(constant.Value), constant);
                    }

                    throw new NotSupportedException(Resources.RefExpressionMustBeConstantValue);
                }
            }
            else if (parameter.IsDefined(typeof(ParamArrayAttribute), true) && argument.NodeType == ExpressionType.NewArrayInit)
            {
                var newArrayExpression = (NewArrayExpression)argument;

                Debug.Assert(newArrayExpression.Type.IsArray);
                var elementType = newArrayExpression.Type.GetElementType()!;

                var n = newArrayExpression.Expressions.Count;
                var matchers = new IMatcher[n];
                var initializers = new Expression[n];

                for (int i = 0; i < n; ++i)
                {
                    (matchers[i], initializers[i]) = MatcherFactory.CreateMatcher(newArrayExpression.Expressions[i]);
                    initializers[i] = initializers[i].ConvertIfNeeded(elementType);
                }

View on GitHub (pinned to 89a5be629c)