You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 17:09:25