C#业务逻辑单元测试求助:GetUser测试返回空值问题
如何为C#业务逻辑层的GetUser方法编写单元测试?
问题背景
正在学习C#,需要为业务逻辑层的GetUser方法编写单元测试。该方法逻辑为:从数据访问层获取用户,若用户不存在则创建新用户后返回,但编写的测试中GetUser始终返回空值,不符合预期。
相关代码
数据访问层方法
public void CreateUser(string userName, int phoneNumber, int age) { using MySqlCommand mySqlCommand = new MySqlCommand("usp_CreateUser"); mySqlCommand.CommandType = CommandType.StoredProcedure; mySqlCommand.Parameters.AddWithValue("UserName", userName); mySqlCommand.Parameters.AddWithValue("TailLength", iconSize); mySqlCommand.Parameters.AddWithValue("IconSize", tailLength); ExecuteNonQuery(mySqlCommand); } public UserSettings UpdateUser(string userName, User user) { if (GetUser(userName) == null) return null; using MySqlCommand updateCommand = new("usp_UpdateUserByUserName"); updateCommand.CommandType = CommandType.StoredProcedure; updateCommand.Parameters.AddWithValue("userName", userName); updateCommand.Parameters.AddWithValue("phoneNumber", userSetting.PhoneNumber); updateCommand.Parameters.AddWithValue("age", userSetting.Age); ExecuteNonQuery(updateCommand); return GetUserSetting(userName); }
User模型
public class User { public int? PhoneNumber { get; set; } public int? Age { get; set; } }
业务逻辑层方法
public User GetUser(string userName) { var userSetting = _userDataAccess.GetUser(userName); if(user == null) { _userDataAccess.CreateUser(userName, AppSettings.GetCurrentSettings().PhoneNumber, AppSettings.GetCurrentSettings().Age); user = _userDataAccess.GetUser(userName); } return user; } public User UpdateUser(string userName, User user) { var updatedUser = _userDataAccess.UpdateUser(userName, user); if (updatedUser == null) { throw new HttpNotFoundException("User does not exist"); } return updatedUser; }
现有测试代码及问题
编写的测试中GetUser返回空值,不符合预期:
public class BusinessLogicTests { private Mock<IUserDataAccess> _mockDataAccess; private UserBusinessLogic _userBusinessLogic; private User user; public BusinessLogicTests() { _mockDataAccess = new Mock<IUserDataAccess>(); _userBusinessLogic = new UserBusinessLogic(_mockDataAccess.Object); user = new User() { PhoneNumber = 1, Age = 1 }; } [Test] public void GetUserShouldReturnUser() { _mockDataAccess.Setup(g => g.CreateUser("test", 1, 1)); //Arrange User user = _userBusinessLogic.GetUser("test"); Assert.NotNull(user); } }
解决方案
1. 修复业务逻辑层的代码错误
原GetUser方法存在变量名不匹配问题,user变量未初始化就进行空值判断,修正后:
public User GetUser(string userName) { var user = _userDataAccess.GetUser(userName); if(user == null) { var settings = AppSettings.GetCurrentSettings(); _userDataAccess.CreateUser(userName, settings.PhoneNumber, settings.Age); user = _userDataAccess.GetUser(userName); } return user; }
2. 隔离静态依赖AppSettings
静态方法AppSettings.GetCurrentSettings()会导致测试无法控制返回值,改为依赖注入模式:
定义接口
public interface IAppSettings { int PhoneNumber { get; } int Age { get; } }
实现接口
public class AppSettings : IAppSettings { public int PhoneNumber { get; set; } public int Age { get; set; } public static IAppSettings GetCurrentSettings() { // 实际从配置读取的逻辑 return new AppSettings { PhoneNumber = 123456, Age = 30 }; } }
修改业务逻辑层构造函数
public class UserBusinessLogic { private readonly IUserDataAccess _userDataAccess; private readonly IAppSettings _appSettings; public UserBusinessLogic(IUserDataAccess userDataAccess, IAppSettings appSettings) { _userDataAccess = userDataAccess; _appSettings = appSettings; } public User GetUser(string userName) { var user = _userDataAccess.GetUser(userName); if(user == null) { _userDataAccess.CreateUser(userName, _appSettings.PhoneNumber, _appSettings.Age); user = _userDataAccess.GetUser(userName); } return user; } // 其他方法... }
3. 编写正确的单元测试
使用Moq模拟依赖,设置正确的返回值并验证方法调用:
public class BusinessLogicTests { private Mock<IUserDataAccess> _mockDataAccess; private Mock<IAppSettings> _mockAppSettings; private UserBusinessLogic _userBusinessLogic; private User _testUser; [SetUp] public void Setup() { _mockDataAccess = new Mock<IUserDataAccess>(); _mockAppSettings = new Mock<IAppSettings>(); // 设置AppSettings返回测试值 _mockAppSettings.Setup(s => s.PhoneNumber).Returns(1); _mockAppSettings.Setup(s => s.Age).Returns(1); _userBusinessLogic = new UserBusinessLogic(_mockDataAccess.Object, _mockAppSettings.Object); _testUser = new User() { PhoneNumber = 1, Age = 1 }; } [Test] public void GetUser_WhenUserDoesNotExist_CreatesAndReturnsUser() { // Arrange // 第一次GetUser返回null,触发创建逻辑;第二次返回测试用户 _mockDataAccess.SetupSequence(d => d.GetUser("test")) .Returns((User)null) .Returns(_testUser); // 验证CreateUser的调用参数 _mockDataAccess.Setup(d => d.CreateUser("test", 1, 1)); // Act var result = _userBusinessLogic.GetUser("test"); // Assert Assert.NotNull(result); Assert.AreEqual(_testUser.PhoneNumber, result.PhoneNumber); Assert.AreEqual(_testUser.Age, result.Age); // 验证CreateUser被调用一次 _mockDataAccess.Verify(d => d.CreateUser("test", 1, 1), Times.Once); // 验证GetUser被调用两次 _mockDataAccess.Verify(d => d.GetUser("test"), Times.Exactly(2)); } [Test] public void GetUser_WhenUserExists_ReturnsExistingUser() { // Arrange _mockDataAccess.Setup(d => d.GetUser("existingUser")).Returns(_testUser); // Act var result = _userBusinessLogic.GetUser("existingUser"); // Assert Assert.NotNull(result); Assert.AreEqual(_testUser.PhoneNumber, result.PhoneNumber); // 验证CreateUser未被调用 _mockDataAccess.Verify(d => d.CreateUser(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never); // 验证GetUser仅被调用一次 _mockDataAccess.Verify(d => d.GetUser("existingUser"), Times.Once); } }
核心问题总结
- 原测试未设置
GetUser的返回值,导致两次调用都返回null,最终结果为空。 - 业务逻辑层存在变量名混用的bug,导致空值判断逻辑失效。
- 静态依赖
AppSettings无法在测试中控制,必须改为依赖注入实现隔离。
内容的提问来源于stack exchange,提问作者Andy Orchard
相关产品推荐
相关产品推荐

