Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I stub/mock services in ASP.NET Core tests

I'd like to test some classes that are part of ASP.NET Web Api project. I don't need request-response integration tests via TestServer (though they're nice) but I want to keep my tests as close to "real thing" as possible. So I want to resolve my classes using services added in Startup but changing some of them by stubs/mocks on test basis (some tests need mocks, others - don't).

It was really easy to do in good old days when ASP.NET did not have internal dependency injection framework. So I would just call a class that registers all the dependencies to a container, then create child container for every test, change some dependencies to mocks and that's it.

I tried something like this:

    var host = A.Fake<IHostingEnvironment>();
    var startup = new Startup(host);
    var services = new ServiceCollection();
    //Add stubs here
    startup.ConfigureServices(services);
    var provider = services.BuildServiceProvider();
    provider.GetService<IClientsHandler>();

It seems to work but I don't want to create the whole startup infrastructure for every test. I'd like to create it once and then create "child container" or "child scope" for each test. Is it possible? Basically I'm looking for a way to modify services outside Startup.

like image 556
SiberianGuy Avatar asked Mar 01 '17 16:03

SiberianGuy


3 Answers

Creating child scopes for every request can be done when configuring your HttpConfiguration by creating a custom IHttpControllerActivator.

This is for OWIN, but converting to .Net Core should be pretty simple: https://gist.github.com/jt000/eef096a2341471856e8a86d06aaec887

The important parts are for creating a Scope & Controller in that scope...

var scope = _provider.CreateScope();
request.RegisterForDispose(scope);

var controller = scope.ServiceProvider.GetService(controllerType) as IHttpController;

...and overwriting the default IHttpControllerActivator...

config.Services.Replace(typeof (IHttpControllerActivator), new ServiceProviderControllerActivator(parentActivator, provider));

Now you can add your controllers to be created via the IServiceProvider with scoped Dependency Injection...

services.AddScoped<ValuesController>((sp) => new ValuesController(sp.GetService<ISomeCustomService>()));

To test your ValuesController within your unit tests, I would suggest using something like the Moq framework to mock out the methods in your service interfaces. For example:

var someCustomService = Mock.Of<ISomeCustomService>(s => s.DoSomething() == 3);
var sut = new ValuesController(someCustomService);

var result = sut.Get();

Assert.AreEqual(result, new [] { 3 });
like image 158
jt000 Avatar answered Oct 26 '22 21:10

jt000


Just in case you would like to use the same Web API Core controllers and DI infrastructure for your xUnit unit tests (I would call them integration tests in this case), I would propose to move TestServer and HttpClient context to the base class implementing xUnit IClassFixture.

In this case you will test API with all the services and DI configured in you your real Web API Core:

public class TestServerDependent : IClassFixture<TestServerFixture>
{
    private readonly TestServerFixture _fixture;
    public TestServer TestServer => _fixture.Server;
    public HttpClient Client => _fixture.Client;

    public TestServerDependent(TestServerFixture fixture)
    {
        _fixture = fixture;
    }

    protected TService GetService<TService>()
        where TService : class
    {
        return _fixture.GetService<TService>();
    }
}

public class TestServerFixture : IDisposable
{
    public TestServer Server { get; }
    public HttpClient Client { get; }

    public TestServerFixture()
    {
        // UseStaticRegistration is needed to workaround AutoMapper double initialization. Remove if you don't use AutoMapper.
        ServiceCollectionExtensions.UseStaticRegistration = false;

        var hostBuilder = new WebHostBuilder()
            .UseEnvironment("Testing")
            .UseStartup<Startup>();

        Server = new TestServer(hostBuilder);
        Client = Server.CreateClient();
    }

    public void Dispose()
    {
        Server.Dispose();
        Client.Dispose();
    }

    public TService GetService<TService>()
        where TService : class
    {
        return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
    }
}

Then you can just derive from this class to test controller action this way:

public class ValueControllerTests : TestServerDependent
{
    public ValueControllerTests(TestServerFixture fixture)
        : base(fixture)
    {
    }

    [Fact]
    public void Returns_Ok_Response_When_Requested()
    {
        var responseMessage = Client.GetAsync("/api/value").Result;
        Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
    }
}

Also you can test the DI services:

public class MyServiceTests : TestServerDependent
{
    public MyServiceTests(TestServerFixture fixture)
        : base(fixture)
    {
    }

    [Fact]
    public void ReturnsDataWhenServiceInjected()
    {
        var service = GetService<IMyService>();
        Assert.NotNull(service);

        var data = service.GetData();
        Assert.NotNull(data);
    }
}
like image 28
Dmitry Pavlov Avatar answered Oct 26 '22 20:10

Dmitry Pavlov


It's all well covered in the documentation.

For integration tests, you use TestServer class and give it a Startup class (do not have to be the live startup, can also be StartupIntegrationTest or Startup with Configure{Envrionment Name here} / ConfigureServices{Envrionment Name here} method.

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

To access the service provider, do

var server = new TestServer(new WebHostBuilder()
    .UseStartup<Startup>()
    .UseEnvironment("IntegrationTest")); 
var controller = server.Host.Services.GetService<MyService>();

For Unit tests you shouldn't use IServiceCollection/IServiceProvider at all, just mock the interfaces and inject them.

like image 35
Tseng Avatar answered Oct 26 '22 21:10

Tseng