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

ASP.NET Core MVC中IDataProtectionProvider相关单元测试异常求助

问题:ASP.NET Core MVC控制器Action单元测试报错:无法创建CryptoParamsProtector类代理

错误信息

Castle.DynamicProxy.InvalidProxyConstructorException : 无法创建POSModels.CryptoParamsProtector类的代理。未找到无参构造函数。

堆栈跟踪

ProxyGenerator.CreateClassProxyInstance(Type proxyType, List1 proxyArguments, Type classToProxy, Object[] constructorArguments) ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors) CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) line 62 Mock1.InitializeInstance() line 311
Mock1.OnGetObject() line 325 Mock.get_Object() line 179 Mock1.get_Object() line 283
UnitTypesControllerTests.initController(Object obj) line 80
UnitTypesControllerTests.IndexTest() line 86

错误抛出点为initController方法的最后一行。以下是相关代码:

单元测试代码

namespace PWEB.Controllers.Tests
{
    using Xunit;

    public class UnitTypesControllerTests
    {
        private Mock<IHttpContextAccessor> contextAccessor;
        private UnitTypesController unitTypesController;
        private Mock<NetCorePOSSystemConfig> config;
        private Mock<IHttpClientFactory> httpClientFactory;
        private Mock<Microsoft.Extensions.Options.IOptions<NetCorePOSSystemConfig>> configure;
        private Mock<Microsoft.AspNetCore.Http.HttpContext> mockHttpContext;
        private Mock<MockHttpMessageHandler> mock;
        private Mock<CryptoParamsProtector> cryptoParams;
        PaginationEntity<UnitTypeModel> paginationEntity = new PaginationEntity<UnitTypeModel>()
        {
            Items = new List<UnitTypeModel>(),
            MetaData = new PaginationMetaData()
            {
                Count = 1,
                TotalItemCount = 1,
                PageNumber = 1,
                FirstItemOnPage = 1,
                HasNextPage = false,
                HasPreviousPage = false,
                IsFirstPage = true,
                IsLastPage = false,
                LastItemOnPage = 1,
                PageCount = 1,
                PageSize = 20
            }
        };

        public UnitTypesControllerTests()
        {
            cryptoParams = new Mock<CryptoParamsProtector>(MockBehavior.Strict);
            mock = new Mock<MockHttpMessageHandler>(MockBehavior.Strict);
        }

        private void initController(object obj)
        {
            string value = "1";
            byte[] val = new byte[] { 1 };
            mock.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync(new HttpResponseMessage()
            {
                Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json"),
                StatusCode = System.Net.HttpStatusCode.OK
            }).Verifiable();
            var client = new HttpClient(mock.Object)
            {
                BaseAddress = new Uri("http://xxxxxxxxxxxxxx")
            };

            httpClientFactory = new Moq.Mock<IHttpClientFactory>();
            httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);

            config = new Mock<NetCorePOSSystemConfig>();
            contextAccessor = new Mock<IHttpContextAccessor>();
            mockHttpContext = new Mock<Microsoft.AspNetCore.Http.HttpContext>();

            Mock<ISession> mocks = new Mock<ISession>();
            mocks.Setup(c => c.Set("JWToken", It.IsAny<byte[]>())).Callback<string, byte[]>((k, v) => value = v.ToString());
            mocks.Setup(v => v.TryGetValue("JWToken", out val)).Returns(true);
            mockHttpContext.Setup(v => v.Session).Returns(mocks.Object);
            contextAccessor.Setup(x => x.HttpContext).Returns(mockHttpContext.Object);
            configure = new Mock<Microsoft.Extensions.Options.IOptions<NetCorePOSSystemConfig>>();
            unitTypesController = new UnitTypesController(cryptoParams.Object, httpClientFactory.Object, configure.Object, contextAccessor.Object);
        }

        [Fact()]
        public void IndexTest()
        {
                initController(paginationEntity);
                ViewResult result = unitTypesController.Index(1).Result as ViewResult;
                mock.Verify();
                Assert.IsType<StaticPagedList<UnitTypeModel>>(result.ViewData.Model);
            
        }
}

控制器代码

public class UnitTypesController : Controller
{
    private readonly HttpClient _httpClient;
    private readonly IOptions<NetCorePOSSystemConfig> _options;
    private readonly IHttpContextAccessor contextAccessor;
    private readonly CryptoParamsProtector cryptoParams;
   

    public UnitTypesController(CryptoParamsProtector paramsProtector, IHttpClientFactory httpClientFactory, IOptions<NetCorePOSSystemConfig> config, IHttpContextAccessor httpContext)
    {
       //代码
         ..................
    }
}

加密类代码

public class CryptoParamsProtector
{
        IDataProtector _protector;

        public CryptoParamsProtector(IDataProtectionProvider dataProtectionProvider)
        {
            _protector = dataProtectionProvider.CreateProtector(GetType().FullName);
        }

        // ...........
}

解决方案

问题根源

Moq创建具体类的Mock对象时,默认要求目标类必须有无参构造函数。但你的CryptoParamsProtector类只定义了带IDataProtectionProvider参数的构造函数,没有无参构造函数,所以Moq无法生成代理对象,抛出该错误。

两种解决办法

办法一:抽象接口(推荐,符合依赖倒置原则)

  1. 创建ICryptoParamsProtector接口,定义CryptoParamsProtector中的所有公共方法:
public interface ICryptoParamsProtector
{
    // 这里添加CryptoParamsProtector中需要对外暴露的方法,例如:
    string Protect(string data);
    string Unprotect(string protectedData);
}
  1. 修改CryptoParamsProtector实现该接口:
public class CryptoParamsProtector : ICryptoParamsProtector
{
    IDataProtector _protector;

    public CryptoParamsProtector(IDataProtectionProvider dataProtectionProvider)
    {
        _protector = dataProtectionProvider.CreateProtector(GetType().FullName);
    }

    // 实现接口中的方法
    public string Protect(string data)
    {
        return _protector.Protect(data);
    }

    public string Unprotect(string protectedData)
    {
        return _protector.Unprotect(protectedData);
    }
}
  1. 修改控制器的构造函数,依赖接口而非具体类:
public class UnitTypesController : Controller
{
    private readonly HttpClient _httpClient;
    private readonly IOptions<NetCorePOSSystemConfig> _options;
    private readonly IHttpContextAccessor contextAccessor;
    private readonly ICryptoParamsProtector cryptoParams;

    // 参数改为ICryptoParamsProtector
    public UnitTypesController(ICryptoParamsProtector paramsProtector, IHttpClientFactory httpClientFactory, IOptions<NetCorePOSSystemConfig> config, IHttpContextAccessor httpContext)
    {
       // 原有代码
       ..................
    }
}
  1. 修改单元测试中的Mock对象为接口类型:
// 替换原来的Mock<CryptoParamsProtector>
private Mock<ICryptoParamsProtector> cryptoParams;

public UnitTypesControllerTests()
{
    cryptoParams = new Mock<ICryptoParamsProtector>(MockBehavior.Strict);
    mock = new Mock<MockHttpMessageHandler>(MockBehavior.Strict);
}

办法二:创建Mock时传入构造函数参数(临时方案,不推荐长期使用)

如果不想修改原有类结构,可以在创建CryptoParamsProtector的Mock时,传入其构造函数需要的参数(Mock的IDataProtectionProvider对象):

public UnitTypesControllerTests()
{
    var mockDataProtectionProvider = new Mock<IDataProtectionProvider>();
    // 如果需要用到_protector的方法,还需要继续Mock IDataProtector
    var mockDataProtector = new Mock<IDataProtector>();
    mockDataProtectionProvider.Setup(p => p.CreateProtector(It.IsAny<string>())).Returns(mockDataProtector.Object);
    
    // 传入构造函数参数
    cryptoParams = new Mock<CryptoParamsProtector>(mockDataProtectionProvider.Object, MockBehavior.Strict);
    mock = new Mock<MockHttpMessageHandler>(MockBehavior.Strict);
}

这种方法虽然能解决报错,但会让测试代码更复杂,且不符合依赖倒置原则,建议优先使用办法一。

内容的提问来源于stack exchange,提问作者JagathNalin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:57:07