ivaylokenov/MyTested.AspNetCore.Mvc

NullReferenceException on the basic test - What am I missing?

shawndewet opened this issue · 6 comments

Describe your issue
I have a (rather large) ASP.Net Core 3.1 MVC application. I have gone through steps 1) to 7) defined here, and created the most basic test:
public class HomeControllerShould { [Fact] public void ReturnIndexAction() => MyMvc .Controller<HomeController>() .Calling(c => c.Index()) .ShouldReturn() .View(); }

The test fails with the following output:
Message: System.NullReferenceException : Object reference not set to an instance of an object. Stack Trace: FileProviderRazorProjectFileSystem.GetItem(String path, String fileKind) PageActionDescriptorChangeProvider.ctor(RazorProjectEngine projectEngine, RuntimeCompilationFileProvider fileProvider, IOptions'1 razorPagesOptions) RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSite(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSite(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSite(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) CallSiteVisitor'2.VisitCallSite(ServiceCallSite callSite, TArgument argument) CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) <>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope) ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) ServiceProviderEngine.GetService(Type serviceType) ServiceProvider.GetService(Type serviceType) ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) <>c.<get_InitializationDelegate>b__4_0(IServiceProvider serviceProvider) <>c.<PrepareServices>b__32_0(IInitializationPlugin plugin) EnumerableExtensions.ForEach[T](IEnumerable'1 collection, Action'1 action) TestApplication.PrepareServices(IServiceCollection serviceCollection) TestApplication.Initialize() TestApplication.TryLockedInitialization() TestApplication.get_Services() TestServiceProvider.get_Global() ServiceValidator.ValidateServices() TestServiceProvider.GetService[TInstance]() TestHelper.CreateHttpContextMock() HttpTestContext.ctor() ComponentTestContext.ctor() ActionTestContext.ctor() ActionTestContext'1.ctor() ControllerTestContext.ctor() MyController'1.ctor(Func'1 construction) MyController'1.ctor(TController controller) MyController'1.ctor() MyMvc.Controller[TController]() HomeControllerShould.ReturnIndexAction() line 17

Where do I begin to figure out what the cause is?

Project files
Web Project.cs file attached.
Test Project.cs file attached.

Startup classes
Web Project Startup class attached.
Test Project Startup class attached.

Attachments.zip

Expected behavior
I expect to see a more descriptive error about the cause of this problem.

Screenshots
If applicable, add screenshots to help explain your problem.

Environment:

  • OS: Windows 10 x64
  • ASP.NET Core Version 3.1

Additional context

Hi @shawndewet ! Thank you for trying out my tools. I will take a look at the described issue. Some questions:

  • Do you do anything specific for loading Razor files? I can see FileProviderRazorProjectFileSystem throwing the error.
  • Otherwise - for debugging - the test library loads your startup files, and then the controller action code. So if you put a breakpoint in the action code, and it is not hit - the problem is somewhere in the Startup file. Then you can put a breakpoint in your ConfigureServices or Configure methods so that you can see what is not loaded during the test project and on which line it throws an exception. Usually, it is some sort of configuration missing. You may provide me the line, on which the exception occurs so that I can take a better look.

I found the issue. It is in the .AddRazorRuntimeCompilation() call, and it is a bug in my code, as it does not support it. I will try to find a workaround in the next few hours. If I cannot find one - I will publish a new package to fix the issue.

Found a fix. Add the following class to your test project:

    using Microsoft.AspNetCore.Mvc.Infrastructure;
    using Microsoft.Extensions.Primitives;

    public class TestActionDescriptorChangeProvider : IActionDescriptorChangeProvider
    {
        public IChangeToken GetChangeToken()
        {
            return new CompositeChangeToken(new IChangeToken[0]);
        }
    }

And then in your TestStartup class:

        public void ConfigureTestServices(IServiceCollection services)
        {
            base.ConfigureServices(services);

            services.ReplaceSingleton<IActionDescriptorChangeProvider, TestActionDescriptorChangeProvider>();
        }

Afterward, tests should start to pass. I will still need to upload a fix so that the error is not that difficult to track, and additionally - a plugin package to replace the above service out of the box.

Let me know if you find any other issues.

Great thanks @ivaylokenov! It works now.
Even though my app is pure MVC, and does not use RazorPages, I added the .AddRazorRuntimeCompilation() call to my startup so that any View changes I make during a debug session can be reflected without having to restart the debug session.

@shawndewet No worries, I understand, it saves time. Let me know if you find any other issues. As for this one - I will write back when the plugin package is ready, so I am going to leave it open.

@ivaylokenov I found out the same problem writing simple test case in order to see the work of the library. Thank you for the "hack", but it will be fine if this bug is solved in the next releases of the library. :)

P.P. @ivaylokenov After making the required changes in #377, I receive the same error: "Object reference not set to an instance of an object.".