X

Create fake user for ASP.NET Core controller tests

I think most of ASP.NET Core applications have authentication enabled. When writing unit tests for controllers we have one set of tests that need authenticated user and other set of tests that need anonymous user. Faking User property of controller is a little bit tricky. This blog post shows how to do it.

Source code available! Source code and sample ASP.NET Core solution for this post is available at my GitHub repository gpeipman/AspNetCoreTests.

Testing controllers with authenticated user

Let’s start with my favorite example of controller action that checks if user is authenticated. For anonymous users we have PublicIndex view and for authenticated users there is default Index view. Both are returned from same Index() action of HomeController.

public IActionResult Index()
{
    if (User.Identity.IsAuthenticated)
    {
        return View();
    }

    return View("PublicIndex");
}

We can write unit test to see if Index view is returned for authenticated user. This solution is borrowed from my blog post Faking Azure AD identity in ASP.NET Core unit tests.

[Fact]
public void Index_should_return_private_view_for_authenticated_user()
{
    var logger = new NullLogger<HomeController>();
    var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] {
                                        new Claim(ClaimTypes.NameIdentifier, "SomeValueHere"),
                                        new Claim(ClaimTypes.Name, "gunnar@somecompany.com")
                                        // other required and custom claims
                                   },"TestAuthentication"));
    var controller = new HomeController(logger);    
    controller.ControllerContext = new ControllerContext();    
    controller.ControllerContext.HttpContext = new DefaultHttpContext { User = user };
   
    var result = controller.Index() as ViewResult;
    var viewName = result.ViewName;
   
    Assert.True(string.IsNullOrEmpty(viewName) || viewName == "Index");
}

This test will pass as all conditions for it to work are met.

NB! Authentication provider is important! If we don’t specify authentication provider (“TestAuthentication” in our case) when building ClaimsIdentity then user is not considered as authenticated. The fact that user has name and name identifier claims available doesn’t matter.

I’m using similar trick also for lightweight custom authentication in ASP.NET Core.

Testing controllers with anonymous user

First test idea that probably comes to our minds is simple test like this.

[Fact]
public void Index_should_return_public_view_for_anonymous_user()
{
    var logger = new NullLogger<HomeController>();
    var controller = new HomeController(logger);
   
    var result = controller.Index() as ViewResult;
   
    Assert.Equal("PublicIndex", result.ViewName);
}

But this test fails with NullReferenceException.

It happens on the line where we check if user is authenticated. But why?

There’s always user. When ASP.NET Core application is running there’s always user available but it is possible that user is not authenticated. But there’s always a user.

To make anonymous user test pass we must provide claims identity with no authentication provider.

[Fact]
public void Index_should_return_public_view_for_anonymous_user()
{
    var logger = new NullLogger<HomeController>();
    var user = new ClaimsPrincipal(new ClaimsIdentity());
    var controller = new HomeController(logger);
    controller.ControllerContext = new ControllerContext();
    controller.ControllerContext.HttpContext = new DefaultHttpContext { User = user };
   
    var result = controller.Index() as ViewResult;
   
    Assert.Equal("PublicIndex", result.ViewName);
}

Now the test will pass.

Moving user matters to extension methods

Our tests pass now but we are not yet done. We have repeated code and only two tests this far. Suppose we have few hundreds of tests one day and they all set up identity for controller like shown above. If we want to change it then it’s few hundred unit tests to change. So, it’s time to make some home cleaning.

To keep identity initialization in one place I added extension methods class for ASP.NET controllers.

public static class ControllerTestExtensions
{
    public static T WithIdentity<T>(this T controller, string nameIdentifier, string name) where T: Controller
    {
        controller.EnsureHttpContext();

        var principal = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
                        {
                                new Claim(ClaimTypes.NameIdentifier, nameIdentifier),
                                new Claim(ClaimTypes.Name, name)
                                // other required and custom claims
                        }, "TestAuthentication"));

        controller.ControllerContext.HttpContext.User = principal;

        return controller;
    }

    public static T WithAnonymousIdentity<T>(this T controller) where T : Controller
    {
        controller.EnsureHttpContext();

        var principal = new ClaimsPrincipal(new ClaimsIdentity());

        controller.ControllerContext.HttpContext.User = principal;

        return controller;
    }

    private static T EnsureHttpContext<T>(this T controller) where T : Controller
    {
        if (controller.ControllerContext == null)
        {
            controller.ControllerContext = new ControllerContext();
        }

        if (controller.ControllerContext.HttpContext == null)
        {
            controller.ControllerContext.HttpContext = new DefaultHttpContext();
        }

        return controller;
    }
}

Using these extension methods we can make controller tests shorter.

[Fact]
public void Index_should_return_public_view_for_anonymous_user()
{
    var logger = new NullLogger<HomeController>();
    var controller = new HomeController(logger).WithAnonymousIdentity();

    var result = controller.Index() as ViewResult;

    Assert.Equal("PublicIndex", result.ViewName);
}

[Fact]
public void Index_should_return_private_view_for_authenticated_user()
{
    var logger = new NullLogger<HomeController>();
    var controller = new HomeController(logger)
                            .WithIdentity("SomeValueHere", "gunnar@somecompany.com");

    var result = controller.Index() as ViewResult;
    var viewName = result.ViewName;
   
    Assert.True(string.IsNullOrEmpty(viewName) || viewName == "Index");
}

User related repeated code is gone now and our tests are clean.

Wrapping up

Unit testing ASP.NET Core controllers can be tricky. When web application runs then ASP.NET Core sets up many things automatically and we don’t even notice it. But if we start testing our controllers we will discover these things step by step. Getting User property of controller set up correctly was a little bit tricky but still pretty easy. Using extension methods we were able to keep User property related code in one place and avoid code duplications.

Liked this post? Empower your friends by sharing it!
Categories: ASP.NET Testing

View Comments (4)

Related Post