Open ivaylokenov opened 8 years ago
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
@astian92 Have you tried the above link:
Otherwise, can you give me your action code and the failing test and can give a solution?
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.
@astian92 TestServices
is just a simple class:
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?
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.
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.