dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.32k stars 9.97k forks source link

InvalidOperationException in Microsoft.AspNetCore.TestHost: 'Solution root could not be located using application root' #17632

Closed thomasrea0113 closed 4 years ago

thomasrea0113 commented 4 years ago

Describe the bug

When Using along site XUnit and <IClassFixture> this exception is always thrown when accessing any of the factories properties. I have added all the tests to *.Tests.cs files in the same project, and exclude them from the build when the configuration is Release:

To Reproduce

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <RootNamespace>TaskManager</RootNamespace>
    <GenerateProgramFile>false</GenerateProgramFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="FluentScheduler" Version="5.3.0" />
    <PackageReference Include="NLog" Version="4.6.8" />
    <PackageReference Include="NLog.Schema" Version="4.6.8" />
    <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />

  </ItemGroup>

  <ItemGroup>
    <Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>

  <!-- For testing -->
  <ItemGroup Condition="'$(Configuration)' == 'Debug'">
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
    <PackageReference Include="coverlet.collector" Version="1.0.1" />
  </ItemGroup>

  <!-- If not using debug configuration, exclude all test files -->
  <ItemGroup Condition="'$(Configuration)' != 'Debug'">
    <Compile Remove="**\*.Tests.cs" />
    <Compile Remove="XUnitInjection.cs" />
  </ItemGroup>

</Project>

Test Class

public abstract class TestClass : IClassFixture<TestWebApplicationFactory<Startup>>
    {
        private BindingFlags _propFlags = BindingFlags.Public | BindingFlags.NonPublic // Get public and non-public
            | BindingFlags.Static | BindingFlags.Instance  // Get instance + static
            | BindingFlags.FlattenHierarchy;

        protected TestWebApplicationFactory<Startup> _factory { get; }
        public TestClass(TestWebApplicationFactory<Startup> factory)
        {
            _factory = factory;

            var injected = GetType()
                .GetProperties(_propFlags)
                .Select(p => new
                {
                    property = p,
                    injected = (Injected)p.GetCustomAttributes(typeof(Injected), true).SingleOrDefault()
                })
                .Where(pi => pi.injected != default);

            if (injected.Any())
            {
                using (_factory.Server.Host.Services.CreateScope())
                {
                    foreach (var pi in injected)
                    {
                        var service = pi.injected.Required ?
                            _factory.Services.GetRequiredService(pi.property.PropertyType) :
                            _factory.Services.GetService(pi.property.PropertyType);
                        pi.property.SetValue(this, service);
                    }
                }
            }
        }
    }

This works quite well without the _factory.Server call, and I much prefer this project layout. However, I always receive this exception with that call:

Exception has occurred: CLR/System.InvalidOperationException
An exception of type 'System.InvalidOperationException' occurred in Microsoft.AspNetCore.TestHost.dll but was not handled in user code: 'Solution root could not be located using application root c:\Users\reat\Documents\git-repos\task-manager\TaskManager\bin\Debug\netcoreapp3.0\.'
   at Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(IWebHostBuilder builder, String solutionRelativePath, String applicationBasePath, String solutionName)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.SetContentRoot(IWebHostBuilder builder)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.<EnsureServer>b__20_0(IWebHostBuilder webHostBuilder)
   at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder builder, Action`1 configure)
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
   at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.get_Server()
   at TaskManager.TestClass..ctor(TestWebApplicationFactory`1 factory) in c:\Users\reat\Documents\git-repos\task-manager\TaskManager\XUnitInjection.cs:line 94
   at TaskManager.Controllers.Tests.DataControllerTests..ctor(TestWebApplicationFactory`1 services) in c:\Users\reat\Documents\git-repos\task-manager\TaskManager\Controllers\DataController.Tests.cs:line 13

TestWebApplicationFactory extends WebApplicationFactory. I also tried overriding CreateHostBuilder with the following:


        protected override IHostBuilder CreateHostBuilder()
        {
            var builder = base.CreateHostBuilder();
            builder.ConfigureWebHostDefaults(b => {
                b.UseSolutionRelativeContentRoot("..\\..\\..\\");
            });
            return builder;
        }

but then the same exception is thrown on the call to UseSolutionRelativeContentRoot

Further technical details

dotnet info

.NET Core SDK (reflecting any global.json):
 Version:   3.0.100
 Commit:    04339c3a26

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

Host (useful for support):
  Version: 3.0.0
  Commit:  7d57652f33

.NET Core SDKs installed:
  2.1.701 [C:\Program Files\dotnet\sdk]
  2.2.301 [C:\Program Files\dotnet\sdk]
  3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
thomasrea0113 commented 4 years ago

Just to be sure, I created a separate project with a simple Core web app, and was able to use WebApplicationFactory no problem, so I feel reasonably confident that the issue is that the tests and app are in the same project. I REALLY don't want to have to split them out. I find it so much neater to have Controllers.HomeController.cs and Controllers.HomeController.Tests.cs files, where the *.Tests.cs contain a test class called Controllers.Tests.HomeControllerTests is anyone aware of a workaround for this? I know this isn't really standard dotnet practice, but I find it so much easier to have everything in one project (similar to how the JS libraries and Django handle it).

mkArtakMSFT commented 4 years ago

Hi. Thanks for contacting us. We're closing this issue as there was not much community interest in this ask for quite a while now. You can learn more about our triage process and how we handle issues by reading our Triage Process writeup.