38

In ASP.NET Core 6 default template moves everything from Sturtup.cs into Program.cs, and uses top-level statements in Program.cs, so there's no more (speakable) Program class ether.

That looks awesome, but now, I need to test all of this. WebApplicationFactory<T> still expects me to pass entry-point-class, but I cannot do this (due to it's name now being unspeakable).

How integration tests are expected to be configured in ASP.NET Core 6?

maxc137
  • 2,291
  • 3
  • 20
  • 32
  • You need to extract the service setup into an extension method, then create a startup class in the test project and call those extension methods. Now you can use `WebApplicationFactory` without having one in the app project. – abdusco Sep 04 '21 at 19:29
  • @abdusco Sounds like a hack. This way I wouldn't see most of my configuration in `Program.cs` (which I assume was the intention behind moving `Startup` there). What you are suggesting is create a new `Sturtup` more or less. But thanks for a workaround, that's at least some way to get it working. – maxc137 Sep 04 '21 at 19:40
  • @abdusco Wait, but I would still need to provide some `TStartup`, wouldn't I? – maxc137 Sep 04 '21 at 19:42
  • You can always put the extension class / Startup class in Program.cs. Regardless without introducing an abstraction, how can you specify which lines in Program.cs belong to application configuration? Well, you extract the app setup into a class and give it a name, which you can later refer to in the test project, which is exactly what Startup class is for. Minimal API is cool, but its imperative nature makes some things difficult at the moment. – abdusco Sep 04 '21 at 23:21
  • Also see: https://github.com/martincostello/dotnet-minimal-api-integration-testing – abdusco Sep 04 '21 at 23:26
  • 3
    [Just checked](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-5.0#type-parameters), `T` in `WebApplicationFactory` doesn't have to be the `Startup` class, just some class in the application assembly is enough. – abdusco Sep 04 '21 at 23:30
  • It doesn't seem like I need to create those extension methods. Loke you said, any class from Api assembly will do as a type argument. After I specified random class, everything compiled and all services got configured. However, no controllers got added for some reason (I checked, it executes `AddControllers`). Host is starting and I'm even able to receive Swagger json from it, but it doesn't show any controllers and all api actions return 404. I'm a bit lost here. – maxc137 Sep 04 '21 at 23:53
  • That means the endpoint middleware is not working. Or the endpoint discovery can't find the endpoints defined in the app. – abdusco Sep 05 '21 at 01:13
  • Fixed it with [`AddApplicationPart`](https://stackoverflow.com/a/61812350/4312132). Have no idea why it didn't work without it. I used `Sdk="Microsoft.NET.Sdk.Web"` and added `Microsoft.AspNetCore.Mvc.Testing`. Those seem to be the only pre-reqisits. – maxc137 Sep 05 '21 at 11:29
  • This problem is already solved in RC1 by generating a `Program` class in the background, allowing you to use `WebApplicationFactory`. Don't rush to implement a custom solution for something that may be solved by next Wednesday. [This sample](https://github.com/DamianEdwards/MinimalApiPlayground/blob/main/tests/MinimalApiPlayground.Tests/PlaygroundApplication.cs) uses the nightly RC1 bits and works without extra tricks – Panagiotis Kanavos Sep 09 '21 at 14:38

3 Answers3

34

Note that if you are trying to use xUnit and its IClassFixture<T> pattern, you will run into problems if you just use the InternalsVisibleTo approach. Specifically, you'll get something like this:

enter image description here

"Inconsistent accessibility: base class WebApplicationFactory<Program> is less accessible than class CustomWebApplicationFactory."

Of course you can solve this by making CustomWebApplicationFactory internal but it only moves the problem as now your unit test class will give the same error. When you try to change it there, you will find that xUnit requires that tests have a public constructor (not an internal one) and you'll be blocked.

The solution that avoids all of this and allows you to still use IClassFixture<Program> is to make the Program class public. You can obviously do this by getting rid of the magic no class version of Program.cs, but if you don't want to completely change that file you can just add this line:

public partial class Program { } // so you can reference it from tests

Of course once it's public you can use it from your test project and everything works.

As an aside, the reason why you typically want to prefer using IClassFixture is that it allows you to set up your WebApplicationFactory just once in the test class constructor, and grab an HttpClient instance from it that you can store as a field. This allows all of your tests to be shorter since they only need to reference the client instance, not the factory.

Example:

public class HomePage_Get : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client = new HttpClient();

    public HomePage_Get(CustomWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }
    [Fact]
    public async Task IncludesWelcome()
    {
        HttpResponseMessage response = await _client.GetAsync("/");
        response.EnsureSuccessStatusCode();
        string stringResponse = await response.Content.ReadAsStringAsync();
        Assert.Contains("Welcome.", stringResponse);
    }
}

Finally note that Damian Edwards' MinimalAPIPlayground was updated to use this approach after we discussed the issue. See this commit

ssmith
  • 8,092
  • 6
  • 52
  • 93
  • 1
    Sounds like a workaround.. – maxc137 Oct 08 '21 at 11:48
  • 1
    Oh for sure, that it is. – ssmith Oct 09 '21 at 13:02
  • 1
    `public partial class Program {}` works for me and I didn't have to expose internal types using `InternalsVisibleTo`. Here is the link to the docs that mention testing with `WebApplicationFactory` without a `Startup` class: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#basic-tests-with-the-default-webapplicationfactory – kimbaudi Jan 18 '22 at 23:25
32

The problem is was solved on ASP.NET Core RC1, but as of now (September 20, 2021) the docs are incomplete.

The compiler generates a Program class behind the scenes that can be used with WebApplicationFactory<>. The class isn't public though so the InternalsVisibleTo project setting should be used.

Damien Edwards' Minimal API sample uses the latest nightly bits. The test web app class is declared as :

internal class PlaygroundApplication : WebApplicationFactory<Program>
{
    private readonly string _environment;

    public PlaygroundApplication(string environment = "Development")
    {
        _environment = environment;
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
    ...

In the application project file,InternalsVisibleTo is used to make the Program class visible to the test project:

  <ItemGroup>
    <InternalsVisibleTo Include="MinimalApiPlayground.Tests" />
  </ItemGroup>

RC1 is feature complete and, judging by previous major versions, it will probably be the first version to have a Go Live license, which means it's supported in production.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks for the tip, this does work, but with `InternalsVisibleTo` added to the api project: https://github.com/dotnet/AspNetCore.Docs/issues/23344#issuecomment-921879727 Could you please add it to the answer? Spent some time figuring out why using `Program` fails. – maxc137 Sep 17 '21 at 19:54
  • @МаксимКошевой thanks, I added this – Panagiotis Kanavos Sep 20 '21 at 06:13
  • 1
    I am getting error "System.InvalidOperationException : The entry point exited without ever building an IHost". Reference: https://stackoverflow.com/questions/72633793/integration-test-with-webapplicationfactory-in-asp-net-core-6 – Harsha Jun 19 '22 at 11:59
4

I tried

 <InternalsVisibleTo Include="MinimalApiPlayground.Tests" />

but no cigar! Removed it and added a partial class to program.cs

#pragma warning disable CA1050 // Declare types in namespaces
public partial class Program
{
}
#pragma warning restore CA1050 // Declare types in namespaces

amazingly it worked.

fuzzybear
  • 2,325
  • 3
  • 23
  • 45
  • That's strange `InternalsVisibleTo` worked for me (and continues to work in .Net 6 rc2). Your `Program` class is `public`, so maybe this issue is that you added `InternalsVisibleTo` incorrectly. Maybe name of your test project isn't `MinimalApiPlayground.Tests` – maxc137 Nov 01 '21 at 11:48
  • Hi @maxc137, I did change the name, however, I used the name as in "ABC" and not .Tests (presumabaly a namespace), interesting I'll try again, I'm not sure but I think I prefer in startup as it's easier to remember been caught out with this twice now! – fuzzybear Nov 01 '21 at 15:39