Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.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?

like image 255
Calin Avatar asked Jun 21 '16 09:06

Calin


2 Answers

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.

like image 124
abhijoseph Avatar answered Sep 21 '22 09:09

abhijoseph


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>();
like image 22
Sock Avatar answered Sep 18 '22 09:09

Sock