Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add easier way to unit test controllers calling the extension methods #16

Open
ivaylokenov opened this issue Jul 28, 2016 · 5 comments
Open
Assignees
Milestone

Comments

@ivaylokenov
Copy link
Owner

ivaylokenov commented Jul 28, 2016

Currently, developers need to do this before the tests:

https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting/blob/master/test/AspNet.Mvc.TypedRouting.Test/TestInit.cs

Add Wiki page for unit testing.

@ivaylokenov ivaylokenov self-assigned this Jul 28, 2016
@ivaylokenov ivaylokenov modified the milestones: Backlog, Version 1.2.0 Aug 17, 2016
@astian92
Copy link

astian92 commented Dec 9, 2016

Hi,
I am using Microsoft's testing library, and I can't make this work. The problem, I am guessing, is that I am not sending the serviceCollection instance anywhere after I run this code.
I don't have TestServices object and I am struggling to figure out how to fix it.

Thanks

@ivaylokenov
Copy link
Owner Author

@astian92 Have you tried the above link:

https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting/blob/master/test/AspNet.Mvc.TypedRouting.Test/TestInit.cs

Otherwise, can you give me your action code and the failing test and can give a solution?

@astian92
Copy link

astian92 commented Dec 9, 2016

Yes I did,

I am using this code to call an Initializing method in the Test class constructor:

using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using AspNet.Mvc.TypedRouting;
using AspNet.Mvc.TypedRouting.LinkGeneration;
using System;

namespace Test.Tools.TypedRoutingModels
{
    internal class TypedRoutingInitializer
    {
        private string assembly;

        public TypedRoutingInitializer(string assembly)
        {
            this.assembly = assembly;
        }

        public void Initialize()
        {
            var testAssembly = Assembly.Load(new AssemblyName(this.assembly));

            // Run the full controller and action model building 
            // in order to simulate the default MVC behavior.
            var options = new TestOptionsManager<MvcOptions>();

            var applicationPartManager = new ApplicationPartManager();
            applicationPartManager.FeatureProviders.Add(new ControllerFeatureProvider());
            applicationPartManager.ApplicationParts.Add(new AssemblyPart(testAssembly));

            var modelProvider = new DefaultApplicationModelProvider(options);

            var provider = new ControllerActionDescriptorProvider(
                applicationPartManager,
                new[] { modelProvider },
                options);

            var serviceCollection = new ServiceCollection();
            var list = new List<IActionDescriptorProvider>()
            {
                provider,
            };

            serviceCollection.AddSingleton(typeof(IEnumerable<IActionDescriptorProvider>), list);
            serviceCollection.AddSingleton(typeof(IActionDescriptorCollectionProvider), typeof(ActionDescriptorCollectionProvider));
            serviceCollection.AddSingleton(typeof(IUniqueRouteKeysProvider), typeof(UniqueRouteKeysProvider));
            serviceCollection.AddSingleton(typeof(IExpressionRouteHelper), typeof(ExpressionRouteHelper));
            serviceCollection.AddSingleton(typeof(IUrlHelperFactory), typeof(UrlHelperFactory));

            //TestServices is not recognized ?
            //TestServices.Global = serviceCollection.BuildServiceProvider();
        }

    }
}

As you can see, the "TestServices" object is not recognized by the compiler, and I am sorry if I am missing something simple but I don't know what this service is for either, and my research at the web spawns a long list of different results that I didn't find useful.

Anyway, as this line is not working, I get the following exception:

Test Name:	LogoutTest
Test FullName:	AIM.Portal.UnitTests.Controllers.AccountControllerTests.LogoutTest
Test Source:	..Portal.UnitTests\Controllers\AccountControllerTests.cs : line 238
Test Outcome:	Failed
Test Duration:	0:00:00.0079076

Result StackTrace:	
at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.GetExpresionRouteHelper(IUrlHelper helper)
   at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action[TController](IUrlHelper helper, Expression`1 action, UrlActionContext actionContext)
   at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action[TController](IUrlHelper helper, Expression`1 action)
   at AIM.Portal.Controllers.AccountController.<Logout>d__6.MoveNext() in ...Portal\Controllers\AccountController.cs:line 107
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at AIM.Portal.UnitTests.Controllers.AccountControllerTests.<LogoutTest>d__22.MoveNext() in ...Portal.UnitTests\Controllers\AccountControllerTests.cs:line 239
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message:	
Test method AIM.Portal.UnitTests.Controllers.AccountControllerTests.LogoutTest threw exception: 
System.NullReferenceException: Object reference not set to an instance of an object.

This is my test:

[TestMethod]
public async Task LogoutTest()
{
    var actual = await controller.Logout();
    Assert.IsNotNull(actual);
    Assert.IsInstanceOfType(actual, typeof(RedirectResult));

    var url = (actual as RedirectResult).Url;
    Assert.AreEqual("Home/Index", url);

    this.workerMock.Verify(m => m.SignOut(It.IsAny<HttpContext>()));
}

In the initialization of the test class I use a static tool that calls the initialization that you suggested and mocks the URL function to return the controllername\action as a value:

namespace Test.Tools
{
    public static class Tools
    {
        public static Mock<IUrlHelper> GetTypedRoutingUrlHelperMock()
        {
            var urlHelperMock = new Mock<IUrlHelper>();
            urlHelperMock.Setup(m => m.Action(It.IsAny<UrlActionContext>()))
                .Returns<UrlActionContext>(u => u.Controller + "/" + u.Action);

            return urlHelperMock;
        }

        public static void InitTypeRouting(string assembly)
        {
            var typedRoutingInitializer = new TypedRoutingInitializer(assembly);
            typedRoutingInitializer.Initialize();
        }
    }
}

So, the full initialization code in my test class is:

public AccountControllerTests()
{
    this.workerMock = new Mock<IAccountWorker>();
    Tools.InitTypeRouting("AIM.Portal");
}

[TestInitialize]
public void Setup()
{
    this.controller = new AccountController(this.workerMock.Object);
    this.controller.ControllerContext = new ControllerContext(
        new ActionContext([a httpContextMock], new RouteData(), new ControllerActionDescriptor()));
    this.controller.Url = Tools.GetTypedRoutingUrlHelperMock().Object;
}

Finally, this is the method under test:

public async Task<IActionResult> Logout()
{
    HttpContext.Session.Clear(); //to 'forget' errors or successes for this user
    await worker.SignOut(HttpContext);
    return Redirect(Url.Action<HomeController>(c => c.Index()));
}

Sorry to bother you, and thank you very much for answering.

@ivaylokenov
Copy link
Owner Author

ivaylokenov commented Dec 10, 2016

@astian92 TestServices is just a simple class:

https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting/blob/master/test/AspNet.Mvc.TypedRouting.Test/TestServices.cs

Take a look at the whole example to get a better understand of what is going on.

https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting/tree/master/test/AspNet.Mvc.TypedRouting.Test

Let me know if you have any more questions. 👍

P. S. Have you checked https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc for testing your MVC project?

@rahl0b10
Copy link

rahl0b10 commented Jan 23, 2018

First, thanks for this excellent open source library!

With that, I'm just wondering if there are any plans to move forward on this particular issue. Testing a controller which uses the UrlHelper extension methods throws NullReferenceException like it is going out of style.

I've made my first attempt at implementing the solution above, without much success, but at this point I'm effectively settled on just accepting the confidence my Postman tests give me on my controller functionality. But, it feels pretty wrong and would love to have a more easy to implement unit testing solution :/ Still better than magic strings all over a huge project though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants