如何在使用服务进行验证时对 IValidatableObject Validate 进行单元测试?

How to unit test IValidatableObject Validate when it uses services for validation?

我的 Validate 方法正在访问各种服务进行验证。这些服务是依赖注入的,可以使用 validationContext.GetService 方法访问。在 运行 时间内,这工作正常。

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    var logic = (ShipRegistrationLogic)validationContext.GetService(typeof(ShipRegistrationLogic));
    var userManager = (UserManager<IdentityUser>)validationContext.GetService(typeof(UserManager<IdentityUser>));
    var spaceTransitAuthority = (SpaceTransitAuthority)validationContext.GetService(typeof(ISpaceTransitAuthority));
    var hull = logic.GetHull(HullId);
    var user = userManager.FindByIdAsync(UserId).Result;

    if (spaceTransitAuthority.CheckActualHullCapacity(hull) > logic.GetMaxAuthorizedMass(user))
    {
        yield return new ValidationResult("You do not have the required pilot's license for this hull.", new[] { nameof(HullId) });
    }
}

我想对这个复杂的验证进行单元测试。但是,在执行测试期间,我无法弄清楚如何访问测试评估所需的服务。如GetServicereturnsnull测试期间运行.

我正在使用 MoqxUnit,并且我尝试模拟 ValidationContext 的 GetService 方法但没有成功,因为 ValidationContext 是密封的 class 并且不能被模拟。

这是显示我尝试过的测试:

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void Validate_ShouldPassMassCheck(int hullId)
{
    // Arrange
    var userId = _users[0].Id;
    var viewModel = new ShipRegistrationViewModel()
    {
        UserId = userId,
        HullId = hullId
    };

    var hull = new Hull()
    {
        DefaultMaximumTakeOffMass = TakeOffMassEnum.Tank,
        Id = hullId,
        Name = "Test"
    };

    var shipRegistrationLogicMock = new Mock<ShipRegistrationLogic>();
    var spaceTransitAuthorityMock = new Mock<ISpaceTransitAuthority>();
    shipRegistrationLogicMock.Setup(x => x.GetHull(hullId)).Returns(hull);
    shipRegistrationLogicMock.Setup(x => x.GetMaxAuthorizedMass(_users[0])).Returns((int)TakeOffMassEnum.Tank);
    _userManager.Setup(x => x.FindByNameAsync(userId))
        .ReturnsAsync(new IdentityUser()
        {
            UserName = "user",
        });

    spaceTransitAuthorityMock.Setup(x => x.CheckActualHullCapacity(hull)).Returns((double)TakeOffMassEnum.Tank);

    var context = new Mock<ValidationContext>(viewModel);
    context.Setup(x => x.GetService(typeof(ShipRegistrationLogic))).Returns(shipRegistrationLogicMock);
    context.Setup(x => x.GetService(typeof(UserManager<IdentityUser>))).Returns(_userManager);
    context.Setup(x => x.GetService(typeof(ISpaceTransitAuthority))).Returns(spaceTransitAuthorityMock);

    // Act
    var results = viewModel.Validate(context.Object);

    // Assert
    Assert.Single(results);
    Assert.Equal(ValidationResult.Success, results.First());
}

您可以模拟一个 IServiceProvider 并将其传递到构造函数中:

var services = new Mock<IServiceProvider>();
services.Setup(x => x.GetService(typeof(ShipRegistrationLogic)))
    .Returns(shipRegistrationLogicMock.Object);
services.Setup(x => x.GetService(typeof(UserManager<IdentityUser>)))
    .Returns(_userManager.Object);
services.Setup(x => x.GetService(typeof(ISpaceTransitAuthority)))
    .Returns(spaceTransitAuthorityMock.Object);

var context = new ValidationContext(viewModel, services.Object, null);

// Act
var results = viewModel.Validate(context);

你也可以不用mock直接建一个IServiceProvider

除了嘲笑 IServiceProvider,就像 vernou 说的,你还可以构建一个 IServiceProviver

var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
services.AddSingleton(_ => shipRegistrationLogicMock.Object);
services.AddSingleton<UserManager<IdentityUser>>(_ => _userManager.Object);
services.AddSingleton(_ => spaceTransitAuthorityMock.Object);
var serviceProvider = services.BuidServiceProvider();

// And finally
var context = new ValidationContext(viewModel, serviceProvider, null);