How to Unit Test Startup.cs in .NET Core

前端 未结 3 1902
一向
一向 2020-12-14 01:20

How do people go about Unit Testing their Startup.cs classes in a .NET Core 2 application? All of the functionality seems to be provided by Static extensions methods which a

相关标签:
3条回答
  • 2020-12-14 01:45

    Well yes, if you want to check the fact that extension method AddDbContext was called on services you are in trouble. The good thing is that you shouldn't actually check exactly this fact.

    Startup class is an application composition root. And when testing a composition root you want to check that it actually registers all dependencies required for instantiation of the root objects (controllers in the case of ASP.NET Core application).

    Say you have following controller:

    public class TestController : Controller
    {
        public TestController(ISomeDependency dependency)
        {
        }
    }
    

    You could try checking whether Startup has registered the type for ISomeDependency. But implementation of ISomeDependency could also require some other dependencies that you should check. Eventually you end up with a test that has tons of checks for different dependencies but it does not actually guarantee that object resolution will not throw missing dependency exception. There is not too much value in such a test.

    An approach that works well for me when testing a composition root is to use real dependency injection container. Then I call a composition root on it and assert that resolution of the root object does not throw.

    It could not be considered as pure Unit Test because we use other non-stubbed class. But such tests, unlike other integration tests, are fast and stable. And most important they bring the value of valid check for correct dependencies registration. If such test passes you could be sure that object will also be correctly instantiated in the product.

    Here is a sample of such test:

    [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);
    }
    
    0 讨论(0)
  • 2020-12-14 01:48

    This approach works, and uses the real MVC pipeline, as things should only be mocked if you need to change how they work.

    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
    }
    
    0 讨论(0)
  • 2020-12-14 02:04

    I also had a similar problem, but managed to get around that by using the WebHost in AspNetCore and essentially re-creating what program.cs does, and then Asserting that all of my services exist and are not null. You could go a step further and execute specific extensions for IServices with .ConfigureServices or actually perform operations with the services you created to make sure they were constructed properly.

    One key, is I created a unit test startup class that inherits from the startup class I'm testing so that I don't have to worry about separate assemblies. You could use composition if you prefer to not use inheritance.

    [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) { }
    }
    
    0 讨论(0)
提交回复
热议问题