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?
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.
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); }
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) { } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With