Integration Test Web Api With [Authorize]

雨燕双飞 提交于 2019-12-03 08:14:05

Here is how you should set the Thread.CurrentPrincipal. Add a message handler like this to your Web API project and add the handler in the Register method of WebApiConfig.cs like so: config.MessageHandlers.Add(new MyTestHandler());.

public class MyTestHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
                                 HttpRequestMessage request,
                                     CancellationToken cancellationToken)
    {
        var local = request.Properties["MS_IsLocal"] as Lazy<bool>;
        bool isLocal = local != null && local.Value;

        if (isLocal)
        {
            if (request.Headers.GetValues("X-Testing").First().Equals("true"))
            {
                var dummyPrincipal = new GenericPrincipal(
                                        new GenericIdentity("dummy", "dummy"),
                                          new[] { "myrole1" });

                Thread.CurrentPrincipal = dummyPrincipal;

                if (HttpContext.Current != null)
                    HttpContext.Current.User = dummyPrincipal;
            }
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

This handler sets an authenticated principal to make all your [Authorize] happy. There is an element of risk with this approach. Only for testing, you should plug this handler into the Web API pipeline. If you plug this handler in to the pipeline (intentional or otherwise) in your production code, it basically defeats your authentication mechanism. To mitigate the risk to some extent (hoping API is not accessed locally), I check to ensure the access is local and that there is a header X-Testing with a value of true.

From RestSharp, add the custom header.

var request = new RestRequest(...);
request.AddHeader("X-Testing", "true");

BTW, for integration testing, I'd much rather use in-memory hosting, instead of web-hosting. That way, Web API runs in the same testing project and you can do whatever you want with it, without the fear of breaking something in production. For more info on in-memory hosting, see this and this.

Matt Kocaj

I realise that this question is about firing 'real' requests from RestSharp at the webapi endpoints so this suggestion is not immediately applicable to the OPs scenario.. BUT:

I'm using in-memory Web Api tests using HttpConfiguration, HttpServer and HttpMessageInvoker (much like Badri's suggestion I believe). In this way, I don't need listeners or ports open since I can test the full stack (end to end test) in memory - really handy on a build server, Heroku instance, etc.

Using in-memory tests, here is how you could set the Thread.CurrentPrincipal.. I have a helper on my test base class like this:

protected void AuthentateRequest()
{
    Thread.CurrentPrincipal = new AuthenticatedPrincipal(Thread.CurrentPrincipal);
}

Which uses this:

public class AuthenticatedPrincipal : IPrincipal
{
    private readonly IPrincipal _principalToWrap;
    private readonly IIdentity _identityToWrap;

    public AuthenticatedPrincipal(IPrincipal principalToWrap)
    {
        _principalToWrap = principalToWrap;
        _identityToWrap = new AuthenticatedIdentity(principalToWrap.Identity);
    }

    public bool IsInRole(string role)
    { return _principalToWrap.IsInRole(role); }

    public IIdentity Identity
    {
        get { return _identityToWrap; }
        private set { throw new NotSupportedException(); }
    }
}

public class AuthenticatedIdentity : IIdentity
{
    private readonly IIdentity _identityToWrap;

    public AuthenticatedIdentity(IIdentity identityToWrap)
    {
        _identityToWrap = identityToWrap;
    }

    public string Name
    {
        get { return _identityToWrap.Name; }
        private set { throw new NotSupportedException(); }
    }
    public string AuthenticationType
    {
        get { return _identityToWrap.AuthenticationType; }
        private set { throw new NotSupportedException(); }
    }
    public bool IsAuthenticated
    {
        get { return true; }
        private set { throw new NotSupportedException(); }
    }
}

It may seem like overkill to stub the IPrincipal manually but I tried with my mocking framework and it blew up in some of my test runners (Resharper and TeamCity, but not NCrunch - something about serialising over AppDomains I think).

This will set Thread.CurrentPrincipal inside the ApiController action method and therefore fool the AuthorizeAttribute into believing you are authenticated.

Set the authenticator for your RestClient:

RestClient.Authenticator = new HttpBasicAuthenticator(username, password);

Using the authenticator that your custom login system actually accepts ... Basic, NTLM, OAuth, Simple ...

It is kind of documented in the second line of the example at http://restsharp.org/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!