devlooped/moq · error · ArgumentException
Unsupported expression
Error message
Unsupported expression: {0} What it means
ExpressionExtensions.Split decomposes a lambda expression into its member-access chain pieces (e.g. `x => x.A.B.C` into A, B, C). If, after stripping member accesses, the remainder is not a simple ParameterExpression (the lambda's root parameter), the expression is not a plain chain and Moq throws ArgumentException 'Unsupported expression: {0}'.
Solutions
- Rewrite the lambda as a pure member chain rooted at the parameter: `x => x.A.B`, no method calls, casts, operators, or null-conditionals.
- For method-call roots, use the appropriate Setup overload (e.g. `Setup(x => x.A())`) rather than Split-based APIs.
- Hoist conditional/default logic out of the expression: compute values outside and use `.Returns(...)` for them.
- Print the expression with ToStringFixed() from the message to identify which node breaks the chain.
Example fix
// before mock.Setup(x => x.Bar?.Baz); // null-conditional -> unsupported // after mock.Setup(x => x.Bar.Baz);
Defensive patterns
Strategy: type-guard
Validate before calling
// Accept only plain parameter-rooted chains
static bool IsSplitSafe(LambdaExpression e) =>
StripMembers(e.Body) is ParameterExpression; Type guard
static bool IsMemberChain<T, R>(Expression<Func<T, R>> e) =>
e.Body is MemberExpression me && StripToParameter(me) is ParameterExpression; Try / catch
try
{
var parts = expression.Split();
}
catch (ArgumentException ex) when (ex.Message.StartsWith("Unsupported expression"))
{
// rewrite lambda as a plain chain or use a different Setup API
} Prevention
- No null-conditional (?.), ??, casts, or method calls in chain lambdas
- Root every lambda at the mock parameter, not a local/captured variable
- Split complex logic: navigate chains in the expression, compute values via Returns
- Test setups after upgrades that might change expression shapes
When it happens
Trigger: Calling Split (via ToArray/Setup helpers) with a lambda whose body is not rooted at the parameter: indexers, method calls at the root, `x.A()`, casts, conversions, or expressions like `x => x.A.B ?? something`, `x => new Foo().Bar`, or a captured variable instead of the parameter.
Common situations: Passing compound expressions where a plain property chain is required; using `?.` (null-conditional) operators which compile to complex trees; boxing/cast wrappers `(IFoo)x.A`; setups written against local variables instead of the mock parameter.
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
- Unsupported expression
- Expression is not a property access
- Expression involves a field access, which is not supported…
- It is impossible to call the provided strongly-typed…
- Can't set return value for void method
AI-assisted analysis of devlooped/moq@89a5be629c (2026-09-16).
Data as JSON: /api/errors/294f8c9026247b0d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Moq/ExpressionExtensions.cs:164
{
Debug.Assert(expression != null);
var parts = new Stack<MethodExpectation>();
Expression remainder = expression.Body;
while (CanSplit(remainder))
{
Split(remainder, out remainder, out var part, allowNonOverridableLastProperty: allowNonOverridableLastProperty && parts.Count == 0);
parts.Push(part);
}
if (parts.Count > 0 && remainder is ParameterExpression)
{
return parts;
}
else
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
Resources.UnsupportedExpression,
remainder.ToStringFixed()));
}
void Split(Expression e, out Expression r /* remainder */, out MethodExpectation p /* part */, bool assignment = false, bool allowNonOverridableLastProperty = false)
{
const string ParameterName = "...";
switch (e.NodeType)
{
case ExpressionType.Assign: // assignment to a property or indexer
case ExpressionType.AddAssign: // subscription of event handler to event
case ExpressionType.SubtractAssign: // unsubscription of event handler from event
{
var assignmentExpression = (BinaryExpression)e;
Split(assignmentExpression.Left, out r, out var lhs, assignment: true);View on GitHub (pinned to 89a5be629c)