Orchard Core 中執行帶程式上下文的單元測試
Orchard Core 帶有很多單元測試,使用 Xunit 單元測試框架,除了簡單的直接呼叫待測試的方法,有一些複雜的測試是需要上下文的,甚至需要 Application 程式啟動起來,Orchard Core 的例子中有一個基於 HTTP 的 Application 測試,但是其測試都是通過呼叫 HTTP API 執行的,測試 Controller 挺方便,但是測試 Service 等就麻煩了,而且測試往往是需要呼叫內部的一些方法的,所以 HTTP API 測試適用範圍有限。
`WebApplicationFactory` can only test by http method, could not direct call Application's component, like do some Session.Query() then direct call some method to do a complex test.
所以自己做了個能夠啟動 Application 且在Application 上下文內執行測試的單元測試基類和輔助方法。使用方便,繼承即可使用,然後你就可以像在 Orchard Core 內部寫程式碼一樣,去呼叫各種 Service、Query 進行測試啦。
由於是從我給 Orchard Core 團隊提的 issue 裡面整理拷貝而來,中英文混合,將就著看,主要把我的實現程式碼分享,方便有需要的人。
public class AppTestBase<TFixture> : IClassFixture<TFixture> where TFixture : class, IApplicationStartupFixture
{ public readonly TFixture Application; public AppTestBase(TFixture application) { this.Application = application; } protected T GetService<T>() where T : class { return this.Application.ServiceProvider.GetService<T>(); }protected T GetRequiredService<T>() where T : class { return this.Application.ServiceProvider.GetRequiredService<T>(); } protected async Task RunInShellScopeAsync(Func<ShellScope, Task> execute) { var shellContextFactory = GetRequiredService<IShellContextFactory>(); IShellHost shellHost = GetRequiredService<IShellHost>(); using (var shell = await shellContextFactory.CreateDefaultShellContext()) using (var shellScope = shell.CreateScope()) { var httpContextAccessor = shellScope.ServiceProvider.GetService<IHttpContextAccessor>(); httpContextAccessor.HttpContext = shellScope.ShellContext.CreateHttpContext(); await shellScope.UsingAsync(execute); } } protected T CreateController<T>(ShellScope shellScope, HttpContext httpContext) { var controllerActivatorProvider = shellScope.ServiceProvider.GetService<IControllerActivatorProvider>(); var controllerContext = new ControllerContext() { HttpContext = httpContext, }; var controllerObj = (T) controllerActivatorProvider.CreateActivator( new ControllerActionDescriptor() {ControllerTypeInfo = (TypeInfo) typeof(T),}) .Invoke(controllerContext); return controllerObj; } }
add database setting in ShellSettings (required):
settings["DatabaseProvider"] ="SqlConnection"; settings["ConnectionString"] = "Server=localhost;Database=xxx;User Id=sa;Password=xxxxxxx"; settings["TablePrefix"] = "xxx";
public static class TestShellContextFactoryExtensions { internal static Task<ShellContext> CreateDefaultShellContext(this IShellContextFactory shellContextFactory) { var settings = new ShellSettings() { Name = ShellHelper.DefaultShellName, State = TenantState.Running }; settings["DatabaseProvider"] ="SqlConnection"; settings["ConnectionString"] = "Server=localhost;Database=xxx;User Id=sa;Password=xxxxxxx"; settings["TablePrefix"] = "xxx"; return shellContextFactory.CreateDescribedContextAsync(settings, new ShellDescriptor() { Features = new List<ShellFeature>() { new ShellFeature("Application.Default"), new ShellFeature("OrchardCore.Setup"), new ShellFeature("Operational"), }, Parameters = new List<ShellParameter>(), }); // return shellContextFactory.CreateShellContextAsync(settings); } }
An helper to create `HttpContext`, and set set `shell.RequestServices` to `HttpContext`.
public static HttpContext CreateHttpContext(this ShellContext shell) { var settings = shell.Settings; var context = new DefaultHttpContext(); // set shell.RequestServices to context context.RequestServices = shell.ServiceProvider; OrchardCore.Modules.HttpContextExtensions.UseShellScopeServices(context); var urlHost = settings.RequestUrlHost?.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); context.Request.Host = new HostString(urlHost ?? "localhost"); if (!String.IsNullOrWhiteSpace(settings.RequestUrlPrefix)) { context.Request.PathBase = "/" + settings.RequestUrlPrefix; } context.Request.Path = "/"; context.Items["IsBackground"] = true; context.Features.Set(new ShellContextFeature { ShellContext = shell, OriginalPathBase = String.Empty, OriginalPath = "/" }); return context; }
使用的例子(先繼承基類):
[Fact] public async Task InAppRuntimeTest() { await RunInShellScopeAsync(async shellScope => { var session = shellScope.ServiceProvider.GetService<ISession>(); Assert.NotNull(session); var controllerObj = CreateController<XxxxxController>(shellScope, shellScope.ShellContext.CreateHttpContext()); var result = await controllerObj.Index(new XxxxModel(){}); Assert.NotNull(result); }); }