dotnet / dotnet/aspnetcore

WebApplicationFactory does not work from when CreateHostBuilder is from another class + assembly, instead of the entry point assembly.

Open
#17,994 10 comments 11 reactions 1 assignee Assigned to @davidfowl View on GitHub
affected-few area-minimal area-mvc enhancement feature-mvc-testing investigate severity-major
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Related

This issue is related to #8210 and (archived) [Hosting #1517](https://github.com/aspnet/Hosting/issues/1517)

---
## Problem

The `WebApplicationFactory` doesn't work right when the "entry point assembly" references _another_ assembly which contains the `CreateHostBuilder` logic. Current code (I think) requires a method called `CreateHostBuilder` to exist in the entry point assembly, only.

---

## Repo

A full repo of this problem [can be found here](https://github.com/PureKrome/WebAppFactoryRepo) in GitHub.

---
## Description

I have a pretty standard ASP.NET Core 3.1 web api application and i'm trying to use the `WebAppFactory` to test my web application.

For my `program.cs` file, instead of adding my custom code in there (e.g. wire up Serilog, etc), I actually have a nuget package which I then re-use everywhere. So if I change my `program.cs` logic, I can just update various webapi applications with the latest nuget, instead of having to change the copy/paste code in each webapi `program.cs` class. Works really well.

Given this type of setup, the `WebApplicationFactory` fails to `ResolveHostBuilderFactory` because the _entry point assembly_ doesn't contain the `CreateHostBuilder` method. And therefore stuff isn't working 100% right.

For example.

this is my `program.cs`

```
public class Program
{
public static Task Main()
{
var options = new MainOptions
{
FirstLoggingInformationMessage = "~~~ Starting FooBar Web Api ~~~"
};

return Homely.AspNetCore.Hosting.CoreApp.Program.Main(options);
}
}
```

That's it. Notice how it's very different to the default template _on first glance_. So my `program.cs` class literally says: -> call some other assembly's `Main` method and do stuff. It's this _other_ `Main(..)` method that contains the typical template logic.

([That `Main` method logic](https://github.com/Homely/Homely.AspNetCore.Hosting.CoreApp/blob/master/src/Homely.AspNetCore.Hosting.CoreApp/Program.cs#L57) is literally -> setup Serilog, do the normal program.cs templated stuff ... then flush the Serilog on exit. So just logging plumbing wrapped around the _default_ program.cs Main logic).

Okay - so my entry point assembly just calls some _other_ assembly. Nothing crazy.

Except ... it doesn't work in this case because the `WebApplicationFactory.CreateHostBuilder()` [code does this](https://github.com/aspnet/AspNetCore/blob/f7f80fdbaaa0283e8e2e89ceed7abf7fe413b17c/src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L318)....

```
protected virtual IHostBuilder CreateHostBuilder()
{
var hostBuilder = HostFactoryResolver.ResolveHostBuilderFactory(typeof(TEntryPoint).Assembly)?.Invoke(Array.Empty());

}
```

and [that `RHBF` method then does this](https://github.com/aspnet/Extensions/blob/master/src/Shared/src/HostFactoryResolver/HostFactoryResolver.cs#L32) ...

```
public static Func ResolveHostBuilderFactory(Assembly assembly)
{
return ResolveFactory(assembly, CreateHostBuilder);
}

private static Func ResolveFactory(Assembly assembly, string name)
{
var programType = assembly?.EntryPoint?.DeclaringType;
if (programType == null)
{
return null;
}


}
```

and the `assembly?.EntryPoint?.DeclaringType` ends up returning `null`. Sadness :(

So .. I'm not sure what can be done here, for this scenario.
It's like I wish the code could 'see' that the entry point assembly is not the right place to check, but _another_ assembly which I tell it, is what should be checked.

Secondly, I also tried this:

```
public class WebAppFactory : WebApplicationFactory
```

but that errored with: `System.InvalidOperationException : The provided Type 'Program' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.` That's _exactly_ what I'm trying to do, though :P

So - would love some help, please?

---

```
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.1.100
Commit: cd82f021f4

Runtime Environment:
OS Name: Windows
OS Version: 10.0.18362
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.100\

Host (useful for support):
Version: 3.1.0
Commit: 65f04fb6db

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.