Calin
Calin

Reputation: 6847

.net core rc2 non trivial integration testing does not work

The following code works as expected and is really similar to the samples found on the asp site:

public abstract class BaseResourceTests : IDisposable
{
    private readonly TestServer _server;

    public HttpClient HttpClient { get; }

    protected BaseResourceTests(string resourceVersion)
    {
        var hostBulider = new WebHostBuilder()
            .UseStartup<Startup>();

        _server = new TestServer(hostBulider);

        HttpClient = _server.CreateClient();
        HttpClient.BaseAddress = new Uri("http://localhost:5000");
    }

    public virtual void Dispose()
    {
        HttpClient.Dispose();
        _server.Dispose();
    }
}

public class EndpointTests : BaseResourceTests
{
    public EndpointTests()
        : base(Resource.VersionHeader)
    {
    }

    [Fact]
    public async Task Post_BodyHasValidDto_ReturnsCreated()
    {
        var dto = new Dto { Name = "someDto" };

        var response = await HttpClient.PostAsJsonAsync("/api/someEndpoint", dto);

        Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.Created);
    }
}

My test works fine it reaches the action in the controller just fine. However in real life the tests are a little more complicated I need to add some services and custom test configuration.

In order to do this I create a TestStartup that inherits from my original project Startup class:

public class TestStartup : Startup
{
    public TestStartup(IHostingEnvironment env) : base(env)
    {
    }

    public override void ConfigureServices(IServiceCollection services)
    {
        base.ConfigureServices(services);
    }

    public override void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        base.Configure(app, env, loggerFactory);
    }
}

As you can see it only delegates to the base class, so now I change the builder to use my TestStartup code:

    protected BaseResourceTests(string resourceVersion)
    {
        var hostBulider = new WebHostBuilder()
            .UseStartup<TestStartup>();

        _server = new TestServer(hostBulider);

        HttpClient = _server.CreateClient();
        HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(resourceVersion));
        HttpClient.BaseAddress = new Uri("http://localhost:5000");
    }

And the tests now fail with with Http NotFound exception. When debugging, it turns out that the action in the controller is no longer hit by the test code.

My guess is that somehow mvc does not detect the routes if the startup file is a different assembly then the controllers, how can I work around this?

Upvotes: 9

Views: 950

Answers (4)

abhijoseph
abhijoseph

Reputation: 317

One more additional change that I had to do to get an overridden startup class to work is to set the ApplicationName in the IHostingEnvironment object to the actual name of the web project.

public TestStartup(IHostingEnvironment env) : base(env)
        {
            env.ApplicationName = "Demo.Web";
        }

This is required when the TestStartup is in a different assembly and overrides the original Startup class. UseContentRoot was still required in my case.

If the name is not set, I always got a 404 not found.

Upvotes: 2

Stefan Hendriks
Stefan Hendriks

Reputation: 4791

You cannot override your services by test services that way. What I did was create methods which added dependencies and made the method overidable. Then in my test I would override that method and inject different dependencies.

I explain this also in my blog: http://www.stefanhendriks.com/2016/04/29/integration-testing-your-dot-net-core-app-with-an-in-memory-database/

Upvotes: 0

Eric Liu
Eric Liu

Reputation: 643

What .net core version are you using? preview1?

Update to RTM (preview2--31...) Maybe you ran into a bug that is already been fixed.

Upvotes: 0

Sock
Sock

Reputation: 5413

I have written a post about doing this sort of integration testing with xUnit here: http://andrewlock.net/introduction-to-integration-testing-with-xunit-and-testserver-in-asp-net-core.


The approach I took was to create a Fixture class which can be injected into your test class.

The Fixture:

public class TestFixture<TStartup> : IDisposable where TStartup : class  
{
    private readonly TestServer _server;

    public TestFixture()
    {
        var builder = new WebHostBuilder().UseStartup<TStartup>();
        _server = new TestServer(builder);

        Client = _server.CreateClient();
        Client.BaseAddress = new Uri("http://localhost:5000");
    }

    public HttpClient Client { get; }

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

The test class then implements IClassFixture<TestFixture<TStartup>>:

public class MiddlewareIntegrationTests : IClassFixture<TestFixture<SystemUnderTest.Startup>>  
{
    public MiddlewareIntegrationTests(TestFixture<SystemUnderTest.Startup> fixture)
    {
        Client = fixture.Client;
    }

    public HttpClient Client { get; }

    [Fact]
    public async Task AllMethods_RemovesServerHeader(string method)
    {
       // Arrange
       var request = new HttpRequestMessage(new HttpMethod("GET"), "/");

       // Act
       var response = await Client.SendAsync(request);

       //assert etc
    }
}

In order to use MVC, and ensure your views can be discovered, you should update your WebHostBuilder to set the content path:

var path = PlatformServices.Default.Application.ApplicationBasePath;
var setDir = Path.GetFullPath(Path.Combine(path, <projectpathdirectory> ));

var builder = new WebHostBuilder()
    .UseContentRoot(setDir)
    .UseStartup<TStartup>();

Upvotes: 1

Related Questions