Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Unit Test Startup.cs in .NET Core

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 aren't mockable?

If you take this ConfigureServices method for example:

public void ConfigureServices(IServiceCollection services) {     services.AddDbContext<BlogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));      services.AddMvc(); } 

How can I write tests to ensure that AddDbContext(...) & AddMvc() are called - the choice of implementing all of this functionality via extension methods seems to have made it untestable?

like image 300
Rob Earlam Avatar asked Nov 25 '17 03:11

Rob Earlam


People also ask

What is Startup Cs in .NET Core?

ASP.NET Core apps use a Startup class, which is named Startup by convention. The Startup class: Optionally includes a ConfigureServices method to configure the app's services. A service is a reusable component that provides app functionality.


2 Answers

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); } 
like image 165
CodeFuller Avatar answered Sep 20 '22 22:09

CodeFuller


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) { } } 
like image 43
War Gravy Avatar answered Sep 22 '22 22:09

War Gravy