使用Moq框架单元测试时Mock接口返回null而非预期对象问题排查
Problem
When unit testing my ApplicationsManager BLL class, I'm using Moq to mock the IApplicationsManagerDal database interface. However, when calling AddIpApplication, the mocked method always returns null instead of the preset IpApplication object I defined.
Relevant Code
BLL Layer (ApplicationsManager):
public ApplicationsManager(IApplicationsManagerDal applicationsManagerDal) { _applicationsManagerDal = applicationsManagerDal ?? throw new Exception("applicationsManagerDAL is null"); } public IpApplication AddIpApplication(IpApplication ipApplication) { try { if (Validator.ExecuteValidation(ipApplication, new AddIpApplicationValidationRules()).ValidationFailures != null) return ipApplication; ipApplication = _applicationsManagerDal.AddIpApplication(ipApplication); ipApplication.Status = CommonStatus.OperationOk; } catch (BaseException e) { ipApplication.Status = e.Status; } catch (Exception e) { ipApplication.Status = ExceptionHandler.SetStatusForException(); NLogger.LogException("IpApplication Name:" + ipApplication.Name, e); } return ipApplication; }
Moq Setup:
var _mock = new Mock<IApplicationsManagerDal>(); _mock.Setup(a => a.AddIpApplication(new IpApplication { Code = new Guid("00000000-B935-4E12-9037-000000000000"), Name = "APPLICATION_ZERO" })) .Returns(() => new IpApplication { Code = new Guid("9631E691-B935-4E12-9037-2E874DB15B0D"), Description = "Test application", Enabled = true, Name = "APPLICATION_ZERO" });
Unit Test:
[TestMethod(), TestCategory("Unit")] public void AddIpApplication_ValidRequest_IpApplicationSuccessfullyAdded() { // Arrange _ipApplication = new IpApplication { Code = new Guid("9631E691-B935-4E12-9037-2E874DB15B0D"), Name = "APPLICATION_ZERO" }; // Act var _ipApplication = _applicationManager.AddIpApplication(_ipApplication); Assert.IsTrue(_ipApplication.Code != Guid.Empty); }
Root Cause
Moq uses reference equality by default when matching method arguments. In your setup, you're creating a brand new IpApplication instance as the parameter matcher. But in your test, you're passing a completely different IpApplication instance (even though some property values match). Since these are two separate objects in memory, Moq doesn't recognize the call as a match for your setup, so it returns the default value for the return type (null for reference types).
Solution
You need to adjust your Moq setup to match the parameter by its property values instead of relying on reference equality. Here are a few ways to do this:
1. Use It.Is<T>() to Match Specific Property Values
This is the most precise approach if you need to verify specific property values are being passed:
_mock.Setup(a => a.AddIpApplication(It.Is<IpApplication>(app => app.Name == "APPLICATION_ZERO" && app.Code == new Guid("9631E691-B935-4E12-9037-2E874DB15B0D")))) .Returns(() => new IpApplication { Code = new Guid("9631E691-B935-4E12-9037-2E874DB15B0D"), Description = "Test application", Enabled = true, Name = "APPLICATION_ZERO" });
2. Implement Value Equality for IpApplication
If you control the IpApplication class, you can override Equals and GetHashCode to compare property values instead of references. Once you do that, Moq will use this custom equality check for parameter matching:
public class IpApplication { // Your existing properties... public override bool Equals(object obj) { if (obj is not IpApplication other) return false; return Code == other.Code && Name == other.Name; } public override int GetHashCode() { return HashCode.Combine(Code, Name); } }
With this in place, your original setup would work as expected.
3. Use It.IsAny<IpApplication>() (If Parameter Specificity Doesn't Matter)
If you don't need to validate the exact parameter being passed and just want to return your preset object for any call to AddIpApplication, use this:
_mock.Setup(a => a.AddIpApplication(It.IsAny<IpApplication>())) .Returns(() => new IpApplication { Code = new Guid("9631E691-B935-4E12-9037-2E874DB15B0D"), Description = "Test application", Enabled = true, Name = "APPLICATION_ZERO" });
Additional Fix for Your Test Code
You have a variable name collision in your test:
// Act var _ipApplication = _applicationManager.AddIpApplication(_ipApplication);
You're reassigning the same _ipApplication variable. Change this to use a distinct variable name to avoid confusion and potential bugs:
// Act var result = _applicationManager.AddIpApplication(_ipApplication); // Assert Assert.IsTrue(result.Code != Guid.Empty);
内容的提问来源于stack exchange,提问作者pampua84

