如何用Moq验证Foo或FooAsync方法至少被调用一次?
Great question—using try/catch for verification definitely feels hacky and makes the test harder to read. Luckily, Moq gives us cleaner ways to check if at least one of two methods was invoked. Let’s walk through a few solid options:
Option 1: Check Mock Invocations Directly
Moq’s Mock<T> exposes an Invocations property that tracks every method call made to the mock. We can use this to check if either Foo or FooAsync appears in the list of invoked methods:
[TestFixture] public class Test { [Test] public void VerifyFooOrFooAsyncCalled() { var mockExample = new Mock<IExample>(); new Thing(mockExample.Object); // Check if either method was called at least once var fooInvoked = mockExample.Invocations.Any(invocation => invocation.Method.Name == nameof(IExample.Foo)); var fooAsyncInvoked = mockExample.Invocations.Any(invocation => invocation.Method.Name == nameof(IExample.FooAsync)); Assert.That(fooInvoked || fooAsyncInvoked, Is.True, "Either Foo() or FooAsync() should have been called"); } }
This approach is straightforward and avoids exception handling entirely. The nameof operator ensures we don’t hardcode method names, making the test more maintainable.
Option 2: Use Callbacks to Track Invocations
Another clean approach is to set up callbacks on each method to flag whether they’ve been called. Then we just need to verify at least one flag is true:
[TestFixture] public class Test { [Test] public void VerifyFooOrFooAsyncCalled_WithCallbacks() { bool fooCalled = false; bool fooAsyncCalled = false; var mockExample = new Mock<IExample>(); mockExample.Setup(e => e.Foo()).Callback(() => fooCalled = true); mockExample.Setup(e => e.FooAsync()).Callback(() => fooAsyncCalled = true); new Thing(mockExample.Object); Assert.That(fooCalled || fooAsyncCalled, Is.True, "Either Foo() or FooAsync() should have been called"); } }
This is especially readable because the intent is explicit—we’re directly tracking which methods get hit during the test.
Option 3: Create a Reusable Extension Method
If you need this "verify either/or" logic across multiple tests, you can wrap it in a custom Moq extension method. This cleans up your test code and makes the verification intent crystal clear:
public static class MoqVerificationExtensions { public static void VerifyAny<T>(this Mock<T> mock, params Expression<Action<T>>[] methodExpressions) where T : class { var anyMethodInvoked = methodExpressions.Any(expr => { try { mock.Verify(expr, Times.AtLeastOnce()); return true; } catch (MockException) { return false; } }); if (!anyMethodInvoked) { var methodNames = string.Join(" or ", methodExpressions.Select(e => ((MethodCallExpression)e.Body).Method.Name)); throw new MockException($"Expected at least one of these methods to be called: {methodNames}"); } } }
Then use it in your test like this:
[TestFixture] public class Test { [Test] public void VerifyFooOrFooAsyncCalled_WithExtension() { var mockExample = new Mock<IExample>(); new Thing(mockExample.Object); // Clean, readable verification mockExample.VerifyAny( e => e.Foo(), e => e.FooAsync() ); } }
This extension method even throws a meaningful error message if neither method was called, which is a big improvement over the original try/catch approach.
Why the Original Try/Catch Approach Is Problematic
The try/catch method works, but it has downsides:
- It’s hard to read at a glance what the test is verifying.
- If neither method is called, you’ll get an exception from the second
Verifycall, which might obscure the actual issue. - It doesn’t handle cases where both methods are called (though that’s not a problem here, it’s still not ideal for clarity).
内容的提问来源于stack exchange,提问作者Philip Pittle

