devlooped/moq · error · NotSupportedException

The return type of the last member shown above is not…

Error message

The return type of the last member shown above is not mockable.

What it means

While intercepting a delegate-based setup, Moq walks the member chain and must return a value for the last member. If that member's return type is not mockable (e.g. a sealed class, struct, or other non-proxyable type), Moq cannot create a proxy to continue the chain and throws NotSupportedException: 'The return type of the last member shown above is not mockable.'

Solutions

  1. Make the last member's return type mockable: change it to an interface or a non-sealed class with a parameterless constructor.
  2. Restructure the setup so the chain stops at the last mockable member and set up the non-mockable value directly.
  3. If the member must return a value type/sealed type, use a conventional `mock.SetupGet(x => x.Member).Returns(value)` instead of chain continuation.
  4. Verify with a mockable-type check (similar to Moq's `IsMockable()`) before writing chain-based setups.

Example fix

// before
mock.SetupAction(x => x.Session.Timestamp.Now); // struct DateTime not mockable
// after
mock.SetupGet(x => x.Session.Timestamp).Returns(new DateTime(2026, 1, 1));
Defensive patterns

Strategy: type-guard

Validate before calling

// Check the last member's return type before chain setup
var returnType = lastMemberProperty.PropertyType;
bool mockable = !returnType.IsValueType && !returnType.IsSealed && returnType.GetConstructor(Type.EmptyTypes) != null;

Type guard

static bool IsMockable(Type t) =>
    !t.IsValueType && (!t.IsSealed || t.IsInterface) && !t.IsPrimitive;

Try / catch

try
{
    mock.SetupAction(lambda);
}
catch (NotSupportedException ex) when (ex.Message.Contains("not mockable"))
{
    // set up the leaf value directly via SetupGet/Returns instead
}

Prevention

When it happens

Trigger: A lambda in an ActionObserver/observer setup accesses a chain whose final property/method returns a non-mockable type — sealed classes, structs, strings, or types without an accessible constructor — so CreateProxy cannot build the intermediate mock.

Common situations: Chaining into a struct-typed property (DateTime, Guid, custom structs); accessing properties returning sealed framework types; deep navigation past the last mockable member; setting up a delegate that reads data rather than invoking an overridable member.

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/fa80eb6a71b42616. Report an issue: GitHub.

Appendix: source

Thrown at src/Moq/ActionObserver.cs:307

                    this.invocation = invocation;
                    this.invocationTimestamp = this.matcherObserver.GetNextTimestamp();

                    if (returnType == typeof(void))
                    {
                        this.returnValue = null;
                    }
                    else if (AwaitableFactory.TryGet(returnType) is { } awaitableFactory)
                    {
                        var result = CreateProxy(awaitableFactory.ResultType, null, this.matcherObserver, out _);
                        this.returnValue = awaitableFactory.CreateCompleted(result);
                    }
                    else if (returnType.IsMockable())
                    {
                        this.returnValue = CreateProxy(returnType, null, this.matcherObserver, out _);
                    }
                    else
                    {
                        throw new NotSupportedException(Resources.LastMemberHasNonInterceptableReturnType);
                    }
                }

                if (returnType != typeof(void))
                {
                    invocation.ReturnValue = this.returnValue;
                }
            }
        }
    }
}

View on GitHub (pinned to 89a5be629c)