NSubstitute参数匹配时Returns被忽略问题排查
Let's break down why your mocked ILicenseChecker.IsEnabled isn't returning the expected enabled value, and how to fix it:
1. Your DI Container Might Not Be Injecting the Mocked License Checker
The most likely culprit here is that MApplication.Container.Resolve<IEntityDefinitionQuerying> isn't actually using the mocked licenseChecker you passed via TypedParameter. Many DI containers prioritize pre-registered instances over inline parameters, so if your container already has a registered implementation of ILicenseChecker, it'll ignore your substituted instance.
How to Verify & Fix:
- Debug Check: After resolving
querying, inspect its internalILicenseCheckerfield (via debugger or reflection) to confirm it's the substitute you created. - Temporary Registration Override: Instead of relying on
TypedParameter, temporarily replace the container's registeredILicenseCheckerwith your mock:public async Task<T> QuerySingleDefinition<T>(Func<IEntityDefinitionQuerying, EntityDefinition, Task<T>> target, bool enabled) { var definition = await CreateDefinition().ConfigureAwait(false); var licenseChecker = Substitute.For<ILicenseChecker>(); licenseChecker.IsEnabled(Arg.Is<EntityDefinition>(p => p.Id == definition.Id), Arg.Any<User>()).Returns(Task.FromResult(enabled)); // Save original registration to restore later var originalRegistration = MApplication.Container.GetRegistration(typeof(ILicenseChecker)); try { // Register the mock as the current instance MApplication.Container.RegisterInstance(licenseChecker); var querying = MApplication.Container.Resolve<IEntityDefinitionQuerying>(); return await target(querying, definition).ConfigureAwait(false); } finally { // Restore original registration to avoid breaking other tests if (originalRegistration != null) { MApplication.Container.Register(originalRegistration); } else { MApplication.Container.Unregister(typeof(ILicenseChecker)); } } }
2. Your Argument Matching Condition Isn't Being Met
Even if the mock is injected correctly, the Arg.Is<EntityDefinition>(p => p.Id == definition.Id) condition might not be triggering. This could happen if:
- The
EntityDefinitionpassed toIsEnabledin yourtargetdelegate is a different instance (e.g., cloned or reloaded) with the same Id (though the Id check should still work if it's a value type likeGuidorint). - The
definition.Idvalue changed between when you created the mock and whenIsEnabledis called. - You're dealing with a reference-type Id (like a custom class) where equality isn't properly implemented.
How to Verify & Fix:
- Log or Debug the Parameters: Add a callback to your mock to inspect the actual parameters being passed:
licenseChecker.When(x => x.IsEnabled(Arg.Any<EntityDefinition>(), Arg.Any<User>())) .Do(call => { var passedDef = call.Arg<EntityDefinition>(); // Compare passedDef.Id to definition.Id here (debug or log) }); - Simplify the Match: If the Id is a value type, you can bypass the object check and match directly on the Id (if
IsEnabledhas an overload that accepts the Id instead of the fullEntityDefinition):licenseChecker.IsEnabled(definition.Id, Arg.Any<User>()).Returns(Task.FromResult(enabled));
3. Method Signature Mismatch
Double-check that the IsEnabled method signature you're mocking exactly matches what's being called in target. For example:
- Are the parameter types correct? (Is the second parameter really
User, or a derived type?) - Are there overloads of
IsEnabledthat you're not accounting for? Iftargetcalls a different overload, your mock configuration won't apply.
How to Verify & Fix:
- Check the
ILicenseCheckerinterface definition to confirm the exact signature ofIsEnabled. - If overloads exist, explicitly specify parameter types in your mock setup to avoid ambiguity:
licenseChecker.IsEnabled(Arg.Is<EntityDefinition>(p => p.Id == definition.Id), Arg.Any<User>()) .Returns(Task.FromResult(enabled));
内容的提问来源于stack exchange,提问作者Gakuo

