[英]How to Unit Test Startup.cs in .NET Core
人們 go 如何在 .NET Core 2 應用程序中對他們的 Startup.cs 類進行單元測試? 所有功能似乎都由 Static 擴展方法提供,這些方法不可模擬?
如果以這個ConfigureServices
方法為例:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
我如何編寫測試以確保AddDbContext(...)
& AddMvc()
- 通過擴展方法實現所有這些功能的選擇似乎使其無法測試?
是的,如果你想檢查在你遇到麻煩的services
調用擴展方法AddDbContext
的事實。 好的是你不應該真正檢查這個事實。
Startup
類是應用程序組合根 。 在測試組合根時,您要檢查它是否實際注冊了根對象實例化所需的所有依賴項(在ASP.NET Core應用程序的情況下為控制器)。
假設您有以下控制器:
public class TestController : Controller
{
public TestController(ISomeDependency dependency)
{
}
}
您可以嘗試檢查Startup
是否已注冊ISomeDependency
的類型。 但是, ISomeDependency
實現還可能需要一些您應該檢查的其他依賴項。 最終,您最終會得到一個針對不同依賴項進行大量檢查的測試,但它實際上並不能保證對象解析不會丟失缺少的依賴項異常。 這樣的測試沒有太多價值。
在測試組合根時,一種適合我的方法是使用真正的依賴注入容器。 然后我在其上調用組合根並斷言根對象的解析不會拋出。
它不能被視為純單元測試,因為我們使用其他非存根類。 但是,與其他集成測試不同,此類測試快速而穩定。 最重要的是,它們為正確的依賴注冊帶來了有效檢查的價值。 如果此類測試通過,您可以確定該對象也將在產品中正確實例化。
以下是此類測試的示例:
[TestMethod]
public void ConfigureServices_RegistersDependenciesCorrectly()
{
// Arrange
// Setting up the stuff required for Configuration.GetConnectionString("DefaultConnection")
Mock<IConfigurationSection> configurationSectionStub = new Mock<IConfigurationSection>();
configurationSectionStub.Setup(x => x["DefaultConnection"]).Returns("TestConnectionString");
Mock<Microsoft.Extensions.Configuration.IConfiguration> configurationStub = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
configurationStub.Setup(x => x.GetSection("ConnectionStrings")).Returns(configurationSectionStub.Object);
IServiceCollection services = new ServiceCollection();
var target = new Startup(configurationStub.Object);
// Act
target.ConfigureServices(services);
// Mimic internal asp.net core logic.
services.AddTransient<TestController>();
// Assert
var serviceProvider = services.BuildServiceProvider();
var controller = serviceProvider.GetService<TestController>();
Assert.IsNotNull(controller);
}
作為@datchung 對 ASP.net Core 6(或 7)最小啟動的回答的替代方法,可以利用WebApplicationFactory<T>
來運行啟動。 請注意,這需要從 API 定義 InternalsVisibleTo 以測試項目的Program
引用是否可訪問。
示例測試,使用 xUnit:
[Fact]
public void StartupTest()
{
var waf = new WebApplicationFactory<Program>();
var server = waf.Server;
// Optional: check for individual services
var myService = server.Services.GetService<IMyService>();
Assert.NotNull(myService);
}
那里的.Server
調用會觸發測試服務器和 ServiceCollection 構建。 反過來,除非“ValidateOnBuild”選項已關閉,否則會觸發驗證。
更多關於 WAF 內部的信息: https://andrewlock.net/exploring-do.net-6-part-6-supporting-integration-tests-with-webapplicationfactory-in-do.net-6/
所有這些都需要您的啟動代碼在測試場景中工作(它不應該連接到在線服務等),但這對於集成測試也很有用(例如 Alba)。
這種方法有效,並使用真正的MVC管道,因為只有當你需要改變它們的工作方式時才應該嘲笑它們。
public void AddTransactionLoggingCreatesConnection()
{
var servCollection = new ServiceCollection();
//Add any injection stuff you need here
//servCollection.AddSingleton(logger.Object);
//Setup the MVC builder thats needed
IMvcBuilder mvcBuilder = new MvcBuilder(servCollection, new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager());
IEnumerable<KeyValuePair<string, string>> confValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("TransactionLogging:Enabled", "True"),
new KeyValuePair<string, string>("TransactionLogging:Uri", "https://api.something.com/"),
new KeyValuePair<string, string>("TransactionLogging:Version", "1"),
new KeyValuePair<string, string>("TransactionLogging:Queue:Enabled", "True")
};
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(confValues);
var confRoot = builder.Build();
StartupExtensions.YourExtensionMethod(mvcBuilder); // Any other params
}
我也遇到了類似的問題,但是設法通過在AspNetCore中使用WebHost來解決這個問題,並且基本上重新創建了program.cs所做的事情,然后斷言我的所有服務都存在並且不是null。 您可以更進一步,使用.ConfigureServices執行IServices的特定擴展,或者實際使用您創建的服務執行操作,以確保它們構造正確。
一個關鍵是,我創建了一個單元測試啟動類,它繼承自我正在測試的啟動類,因此我不必擔心單獨的程序集。 如果您不想使用繼承,則可以使用合成。
[TestClass]
public class StartupTests
{
[TestMethod]
public void StartupTest()
{
var webHost = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder().UseStartup<Startup>().Build();
Assert.IsNotNull(webHost);
Assert.IsNotNull(webHost.Services.GetRequiredService<IService1>());
Assert.IsNotNull(webHost.Services.GetRequiredService<IService2>());
}
}
public class Startup : MyStartup
{
public Startup(IConfiguration config) : base(config) { }
}
就我而言,我使用的是 .NET 6 和最小的 API(沒有 Startup 類)。
我的 Program.cs 原來是這樣的:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
builder.services.AddSingleton<IMyInterface, MyImplementation>();
...
我添加了 StartupHelper.cs:
public class StartupHelper
{
private readonly IServiceCollection _services;
public StartupHelper(IServiceCollection services)
{
_services = services;
}
public void SetUpServices()
{
_services.AddSingleton<IMyInterface, MyImplementation>();
}
}
我在 Program.cs 中使用了 StartupHelper:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
...
我的測試(NUnit)看起來像這樣:
[Test]
public void SetUpServices()
{
var builder = WebApplication.CreateBuilder(new string[0]);
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
var app = builder.Build();
var myImplementation = app.Services.GetService<IMyInterface>();
Assert.NotNull(myImplementation);
Assert.IsTrue(myImplementation is MyImplementation);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.