在ServiceStack OrmLite MVC应用中测试/模拟AuthUserSession的问题
问题背景
现有基于ServiceStack OrmLite的MVC应用,添加控制器测试时,其他依赖注入的模拟均正常,但AuthUserSession始终抛出空引用异常。所有控制器都继承自基础控制器_Controller,其中包含:
public MyAuthUserSession AuthUserSession => SessionAs<MyAuthUserSession>();
MyAuthUserSession继承自AuthUserSession,包含自定义权限检查方法,控制器中会通过该属性做权限校验,例如:
model.CanEdit = AuthUserSession.HasEntityPermission(PermissionType.Update, PermissionEntity.MyEntity);
已尝试但无效的方案
配置测试AppHost:完全照搬生产环境的AuthFeature配置,但会话始终未被填充
public class TestAppHost : AppHostBase { public TestAppHost() : base("Test Service", typeof(LocationsService).Assembly) { } public override void Configure(Container container) { Plugins.Add(new AuthFeature(() => new MyAuthUserSession(), new IAuthProvider[] { new BasicAuthProvider() }) { IncludeAssignRoleServices = false, IncludeAuthMetadataProvider = false, IncludeRegistrationService = false, GenerateNewSessionCookiesOnAuthentication = false, DeleteSessionCookiesOnLogout = true, AllowGetAuthenticateRequests = req => true }); } }手动保存会话:通过AuthenticateService保存测试会话,但与控制器的
SessionAs调用不关联var authService = appHost.Container.Resolve<AuthenticateService>(); var session = new MyAuthUserSession(); authService.SaveSessionAsync(session).Wait();直接注册IAuthSession实例:在AppHost中注册会话实例,无任何效果
TestMode = true; container.Register<IAuthSession>(new MyAuthUserSession { UserAuthId = "1" });修改ControllerContext.HttpContext:尝试往请求项中添加会话,未成功
关键差异发现
API服务中(继承自Service)仅需注册IAuthSession即可正常获取会话,但MVC控制器(继承自ServiceStackController)同样操作无效。简化测试代码后,即使使用空控制器+基础配置的AppHost,调用SessionAs仍抛出空引用:
简化测试代码
TestAppHost配置:
public class TestAppHost : BasicAppHost { public TestAppHost() : base(typeof(LocationsService).Assembly) { } public override void Configure(Container container) { container.Adapter = new UnityIocAdapter(Repository.Container.Instance); Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new BasicAuthProvider(), // HTTP Basic Auth登录 new CredentialsAuthProvider(), // 用户名密码表单登录 })); container.Register<ICacheClient>(new MemoryCacheClient()); var userRepo = new InMemoryAuthRepository(); container.Register<IAuthRepository>(userRepo); container.Register<IAuthSession>(new AuthUserSession()); } }
空控制器:
public class EmptyController: ServiceStackController { public EmptyController() {} public AuthUserSession AuthUserSession => SessionAs<AuthUserSession>(); }
测试调用代码:
_appHost = new TestAppHost(); _appHost.Init(); var contr = _appHost.Resolve<EmptyController>(); var session = contr.AuthUserSession; // 此处抛出空引用异常
异常堆栈跟踪
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) at ServiceStack.Host.NetCore.NetCoreRequest.TryResolve[T]() at ServiceStack.ServiceStackHost.GetCacheClient(IRequest req) at ServiceStack.ServiceStackProvider.get_Cache() at ServiceStack.ServiceStackProvider.SessionAs[TUserSession]() at ServiceStack.Mvc.ServiceStackController.SessionAs[TUserSession]() at WebApplication.Controllers._Controller.get_AuthUserSession() in _Controller.cs:line 93 at WebApplication.Controllers.MyController.Common(PageAction action, MyDataModel model) in MyController.cs:line 288 at WebApplication.Controllers.MyController.Show(Int64 id) in MyController.cs:line 157 at WebApplication.UnitTests.Controllers.MyControllerTests.Show_WithReadPrivileges_ReturnsExpectedSettingValue() in MyControllerTests.cs:line 115
核心需求
由于是遗留应用,无法修改AuthUserSession属性(全项目约1000处引用,重构成本极高),且该属性为常规属性,无法用NSubstitute模拟返回值。需要找到一种无需大规模重构的方式,让ServiceStackController的SessionAs<MyAuthUserSession>()返回指定的测试会话。
内容的提问来源于stack exchange,提问作者Irish Yobbo

