4

I develop integration tests in .NET Core. Because controllers have multiple dependencies it's inconvenient to create their instances manually. Here's how I create ServiceProvider instance to be able to access .NET Core native DI container:

HostingEnvironment env = new HostingEnvironment();
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";

Startup startup = new Startup(env);
ServiceCollection sc = new ServiceCollection();
startup.ConfigureServices(sc);
ServiceProvider = sc.BuildServiceProvider();

Startup class is taken from tested assembly.

To my surprise service provider is unable to create instances of controllers. It can instantiate registered services, but not the controllers. Does anybody have any idea why it is so?

Arkadiusz Kałkus
  • 17,101
  • 19
  • 69
  • 108
  • Did you tried using `TestServer` instead of wiring stuff up yourself? https://learn.microsoft.com/en-us/aspnet/core/testing/integration-testing#integration-testing-aspnet-core – Tseng Feb 14 '17 at 10:29

2 Answers2

5

You should use TestServer, which bootstraps a complete test environment (optionally using a different startup class or environment variable, where you can replace data store with an in-memory store).

The ASP.NET Core documentation covers this case quite well.

var server = new TestServer(new WebHostBuilder()
    .UseStartup<Startup>()
    // this would cause it to use StartupIntegrationTest or ConfigureServicesIntegrationTest / ConfigureIntegrationTest methods (if existing)
    // rather than Startup, ConfigureServices and Configure
    .UseEnvironment("IntegrationTest"));

In these alternative/environment-dependent methods you can put your integration test specific replacements/DI configuration.

Second, controllers (and tag helpers and view components) are not registered with the DI system by default. The controller factories will resolve the types. If you want that the controllers to get resolved via built-in or 3rd party IoC containers, you have to tell ASP.NET Core to register them explicitly (see this related question).

Here is an example:

services
    .AddMvc()
    .AddControllersAsServices()
    .AddViewComponentsAsServices()
    .AddTagHelpersAsServices();

I would recommend you to use the first approach, as it's more consistent and exactly the reason the Startup class with environment support is there.

Update

Important addition. If you use the TestServer, you can also access its DI Container (IServiceProvider instance).

var server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
        .UseEnvironment("IntegrationTest"));
var controller = server.Host.Services.GetService<MyController>();
Community
  • 1
  • 1
Tseng
  • 61,549
  • 15
  • 193
  • 205
-1

Because controllers have multiple dependencies it's inconvenient to create their instances manually.

This should not be the case. When writing unit tests, you should not depend on an DI Container; you should create dependencies manually. Adding a dependency on the container only complicates your tests and adds an unneeded dependency on the library.

There are a few interesting patterns you could apply to make creating your SUT easier. One of which is a variation of the Test Data Builder pattern where you have a private factory method inside the test class to build up the SUT:

private static ShipOrderController CreateController(
    IShipOrderHandler handler = null,
    ILogger logger = null,
    IMailSender mailSender = null)
{
    return new ShipOrderController(
        handler ?? new ShipOrderHandlerStub(),
        logger ?? new LoggerStub(),
        mailSender ?? new MailSenderStub());
}

Such SUT Builder makes your unit tests very readable and maintainable, since the addition of dependencies to the SUT will only affect the factory method; not any existing tests.

A test can look as follows:

[TestMethod]
public void Index_Always_Logs()
{
    // Arrange
    var logger = new FakeLogger();

    var sut = CreateController(logger: logger);

    // Act
    sut.Index();

    // Assert
    Assert.IsTrue(logger.Entries.Any());
}
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 2
    _When writing unit tests, you should not depend on an DI Container; you should create dependencies manually_ reread the question ;) He asks about integration tests – Tseng Feb 14 '17 at 10:28
  • @Tseng: Thanks for pointing this out. You are right, for integration tests it is more naturally to use the same infrastructure (i.e. container) as in production. – Steven Feb 14 '17 at 10:36