user6373040
user6373040

Reputation:

Unit Testing an ASP.Net Web API Controller that relies on the User.Identity object

I'll start by saying that I am reasonably new to unit testing, but I'm a firm believer now. I always had a tester at my previous jobs so I never had to worry about this.

I have an ASP.Net MVC API that is replacing a web service. The API takes an HTTP Post from a web application, logs the request to that database and then makes a request and returns data from another database. The web service that it is replacing had the additional overhead of making sure the user name submitted with the request is the logged in user. To replace this, I am using the site authentication and the associated controller's User.Identity object.

Now I need to write a unit test for this. I can use Moq to mock up an authenticated user for an MVC controller, but can't seem to find how to mock up the authenticated user for an API. Here is what I am trying to do:

... // In the test method
        DbApiController controller = new DbApiController ();
        controller.ControllerContext = GetMockContext();
...

    private HttpControllerContext GetMockContext()
    {
        var identity = new GenericIdentity("testuser");
        var controllerContext = new Mock<HttpControllerContext>();
        var principal = new Mock<IPrincipal>();
        principal.Setup(p => p.IsInRole("SalesAndEstimating"));
        principal.SetupGet(p => p.Identity.Name).Returns("testuser");
        controllerContext.SetupGet(c => c.RequestContext.Principal).Returns(principal.Object);

        return controllerContext.Object;
    }

I am getting the exception:

System.NotSupportedException: Invalid setup on a non-virtual (overridable in VB) member: mock => mock.RequestContext

What am I doing wrong here?

Updated Code:

        ApiController controller = new DbApiController();

        var context = new Mock<HttpContextBase>();

        var identity = new GenericIdentity("testuser");
        identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "1"));
        var principal = new GenericPrincipal(identity, new[] { "user" });
        context.Setup(s => s.User).Returns(principal);

        controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

        var results = ((DbApiController)controller).Post(...);

        Assert.AreEqual(results.Success, true);

After I made the changes above, I still get the error "Argument 3: cannot convert from 'System.Web.Http.ApiController' to 'System.Web.Mvc.ControllerBase'".

Upvotes: 2

Views: 1938

Answers (3)

bryanbcook
bryanbcook

Reputation: 17973

Inspired by Kahbazi's implementation, there's a slightly easier route. This is AspNetCore, though.

// set up identity and required claims
var identity = new GenericIdentity("username");
identity.AddClaim(new Claim("<your-type>", "<your-value>"));

// set up claims principal
var principal = new ClaimsPrincipal(identity);

// set up httpcontext
var context = new DefaultHttpContext()
{
    User = principal
};

// associate httpcontext to controller
controller.ControllerContext = new ControllerContext
{
    HttpContext = context
};

Upvotes: 1

Andrei Dragotoniu
Andrei Dragotoniu

Reputation: 6335

I see this quite a lot and there seems to be a big misunderstanding when it comes to unit testing and also what should or should not be tested and how.

You unit test functionality, business rules, anything that changes data in some way. You don't unit test a controller.

Ideally, you'd want your controllers to be very simple, accept a request, call some service which does whatever needs done and return the result. Controllers should really be very light. If you code like this then you don't need to unit test them, you unit test the services which actually do something.

Now, you still want to test and make sure your API works so your next step is to write integration tests for your endpoints and this is where something like Postman helps. This tool also has the advantage that it can be integrated with a CI server which makes integration tests very easy to add to your pipeline.

So in short, think what you are unit testing, unit test functionality, do not try to test the framework you're using, this is done and should be done by whoever wrote the framework.

Mocking is of course used, but if you mock requests and mock responses from your database, that doesn't mean your code works, the database could work in a different way and throw errors even if your tests pass and that doesn't really make your code any better.

Unit tests are supposed to be small, focused, they do not touch on any external systems, like files on disk, database, other APIs. The reason for this is that you want them to run really fast, even if you have thousands, they need to run in seconds or less so you can run them all the time. As a developer you are supposed to run them all the time to make sure you haven't broken anything.

Don't fall into the trap of adding tests for the sake of bumping up an arbitrary number.

Upvotes: 2

Kahbazi
Kahbazi

Reputation: 14995

You can not setup RequestContext because it's not virtual so it can not be overridden by Mock.

You can use this code

var context = new Mock<HttpContextBase>(); 

var identity = new GenericIdentity("testuser");
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "1"));
var principal = new GenericPrincipal(identity, new[] { "user" } );
context.Setup(s => s.User).Returns(principal);

ApiController controller = new ApiController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

Upvotes: 0

Related Questions