在 .Net Core 中测试控制器总是 returns false?

Test a controller in .Net Core always returns false?

我编写了以下 XUnit 测试来测试 .Net Core WebApi 控制器的操作:

namespace VistaBest.XUnitTest.Api.Test
{
    public class Account_UnitTest
    {
        [Fact]
        public void ValidateUserTest()
        {
            const string username = "admin";
            const string password = "admin";
            var usersBusinessObjectMock = new Mock<IUsersBusinessObject>();
            usersBusinessObjectMock.Setup(service => service.ValidateUser(username, password)).Returns(() => true);
            var controller = new AccountController(usersBusinessObjectMock.Object);
            var actionResult = controller.ValidateUser(new LoginModel
            {
                Username = username,
                Password = password
            });
            var okObjectResult = Assert.IsType<OkObjectResult>(actionResult);
            var result = okObjectResult.Value as bool?;
            Assert.True(result);
        }
    }
}

账户控制器:

namespace VistaBest.Api.Controllers
{
    public class AccountController : BaseController
    {
        private readonly IUsersBusinessObject _usersBusinessObject;
        public AccountController(IUsersBusinessObject usersBusinessObject)
        {
            _usersBusinessObject = usersBusinessObject;
        }

        [HttpPost]
        public IActionResult ValidateUser(LoginModel model)
        {
            if(!ModelState.IsValid) return BadRequest(ModelState);
            return Ok(_usersBusinessObject.ValidateUser(model.Username, model.Password.ToMd5Hash()));
        }
    }
}

IUsersBusinessObject:

namespace VistaBest.Data.BusinessObjects
{
    public interface IUsersBusinessObject
    {
        bool ValidateUser(string username, string password);
        UserModel SelectByUsername(string username);
    }

    public class UsersBusinessObject : BaseBusinessObject, IUsersBusinessObject
    {
        public UsersBusinessObject(IDbConnection connection) : base(connection)
        {

        }

        private const string TableName = "Users";

        public bool ValidateUser(string username, string password)
        {
            var query = $"SELECT COUNT(*) FROM [{TableName}] WHERE UserName = @username and Password = @password";
            return DbConnection.QueryFirst<int>(query, new { username, password }) == 1;
        }
}

如你所见,我说 usersBusinessObjectMock 必须 return true:

usersBusinessObjectMock
    .Setup(service => service.ValidateUser(username, password))
    .Returns(() => true);

但是 var result = okObjectResult.Value as bool?; 总是 false

怎么了?

看完你的代码大约 10 分钟,因为这个错误真的很难找到。你做的一切都是对的...

        var okObjectResult = Assert.IsType<OkObjectResult>(actionResult);
        var result = okObjectResult.Value as bool?;
        Assert.True(result);

但这是错误...

var 很危险... 你的变量 okObjectResult 不是来自 OkObjectResult 类型......这就是为什么你的断言永远不会是真的...... 我确定你不是要输入

Assert.IsType<OkObjectResult>(actionResult);
var okObjectResult = (OkObjectResult) actionResult;

这会起作用,我会在您的代码中进行一些小的重构。

namespace VistaBest.XUnitTest.Api.Test
{
  public class Account_UnitTest
  {
    private readonly Mock<IUsersBusinessObject> _usersBusinessObjectMock;
    private readonly AccountController _accountController;
    public Account_UnitTest() 
    {
       _usersBusinessObjectMock = new Mock<IUsersBusinessObject>();
       _accountController = new AccountController(_usersBusinessObjectMock.Object);
    }

    [Fact]
    public void ValidateUserTest()
    {
        var model = new LoginModel
        {
            Username = "admin",
            Password = "admin"
        };

        _usersBusinessObjectMock.Setup(service => service.ValidateUser(model.Username, model.Password)).Returns(() => true);

        var actual = _accountController.ValidateUser(model) as OkObjectResult;

        actual.Value.ShouldBeEquivalentTo(true); 
        // or Assert.True(actual.Value);
    }
  }
}

在重新检查控制器如何调用验证方法后,我意识到问题是您没有正确配置模拟。

控制器正在调用

_usersBusinessObject.ValidateUser(model.Username, model.Password.ToMd5Hash())

请注意 ToMd5Hash() 正在调用密码。

然而,您将模拟设置为...

usersBusinessObjectMock
    .Setup(service => service.ValidateUser(username, password))
    .Returns(() => true);

看到问题了吗?

模拟需要原始密码而不是它的哈希值,因此在传递哈希密码时不会 return true。这导致操作的对象结果总是 returning false,因为模拟总是 returns false。该方法隐藏在屏幕外,所以我之前没有注意到它。

所以假设 ToMd5Hash() 是一些自定义扩展方法,

您要么设置模拟以期望散列密码与测试中的方法相匹配...

usersBusinessObjectMock
    .Setup(service => service.ValidateUser(username, password.ToMd5Hash()))
    .Returns(() => true);

或使用 It.IsAny<>() 方法来放松对模拟的期望...

usersBusinessObjectMock
    .Setup(service => service.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
    .Returns(() => true);

所以不管你传递给模拟的值是什么,它总是 return true