Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Request.GetOwinContext returns null within unit test - how do I test OWIN authentication within a unit test?

I am currently trying to unit test the authentication for a new WebAPI project I am writing using OWIN to authenticate, and I am having problems with running it in a unit test context.

This is my test method:

[TestMethod]
public void TestRegister()
{
    using (WebApp.Start<Startup>("localhost/myAPI"))
    using (AccountController ac = new AccountController()
        {
            Request = new System.Net.Http.HttpRequestMessage
                (HttpMethod.Post, "http://localhost/myAPI/api/Account/Register")
        })
    {
        var result = ac.Register(new Models.RegisterBindingModel()
        {
            Email = "[email protected]",
            Password = "Pass@word1",
            ConfirmPassword = "Pass@word1"
        }).Result;
        Assert.IsNotNull(result);
    }
}

I'm getting an AggregateException on getting the .Result with the following inner exception:

Result Message: 
Test method myAPI.Tests.Controllers.AccountControllerTest.TestRegister 
    threw exception: 
System.ArgumentNullException: Value cannot be null.
Parameter name: context
Result StackTrace:  
at Microsoft.AspNet.Identity.Owin.OwinContextExtensions
    .GetUserManager[TManager](IOwinContext context)
at myAPI.Controllers.AccountController.get_UserManager()
...

I have confirmed via debugging that my Startup method is being called, calling ConfigurAuth:

public void ConfigureAuth(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    app.UseWebApi(config);

    // Configure the db context and user manager to use a single 
    //  instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>
        (ApplicationUserManager.Create);

    // Enable the application to use a cookie to store information for 
    //  the signed in user
    //  and to use a cookie to temporarily store information about a 
    //  user logging in with a third party login provider
    app.UseCookieAuthentication(new CookieAuthenticationOptions());
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    // Configure the application for OAuth based flow
    PublicClientId = "self";
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/Token"),
        Provider = new ApplicationOAuthProvider(PublicClientId),
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
        AllowInsecureHttp = true
    };

    // Enable the application to use bearer tokens to authenticate users
    app.UseOAuthBearerTokens(OAuthOptions);
}

I've tried a few things, but nothing seems to work - I can never get an OWIN context. The test is failing on the following code:

// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = new ApplicationUser() 
       { UserName = model.Email, Email = model.Email };

    IdentityResult result = await UserManager.CreateAsync(user, model.Password);

    if (!result.Succeeded)
    {
        return GetErrorResult(result);
    }

    return Ok();
}

This calls the UserManager property:

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? Request.GetOwinContext()
           .GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

It fails on:

return _userManager ?? Request.GetOwinContext()
    .GetUserManager<ApplicationUserManager>();

with a NullReferenceException - Request.GetOwinContext is returning null.

So my question is: am I approaching this wrong? Should I just be testing the JSON responses? Or is there a good way to "internally" test OWIN authentication?

like image 313
Codeman Avatar asked Jul 19 '14 04:07

Codeman


4 Answers

GetOwinContext calls context.GetOwinEnvironment(); which is

  private static IDictionary<string, object> GetOwinEnvironment(this HttpContextBase context)
    {
        return (IDictionary<string, object>) context.Items[HttpContextItemKeys.OwinEnvironmentKey];
    }

and HttpContextItemKeys.OwinEnvironmentKey is a constant "owin.Environment" So if you are add that in your httpcontext's Items, it will work.

var request = new HttpRequest("", "http://google.com", "rUrl=http://www.google.com")
    {
        ContentEncoding = Encoding.UTF8  //UrlDecode needs this to be set
    };

    var ctx = new HttpContext(request, new HttpResponse(new StringWriter()));

    //Session need to be set
    var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
        new HttpStaticObjectsCollection(), 10, true,
        HttpCookieMode.AutoDetect,
        SessionStateMode.InProc, false);
    //this adds aspnet session
    ctx.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance,
        null, CallingConventions.Standard,
        new[] { typeof(HttpSessionStateContainer) },
        null)
        .Invoke(new object[] { sessionContainer });

    var data = new Dictionary<string, object>()
    {
        {"a", "b"} // fake whatever  you need here.
    };

    ctx.Items["owin.Environment"] = data;
like image 173
Birey Avatar answered Oct 31 '22 18:10

Birey


To make sure an OWIN context is available during your test (i.e., to fix the null reference exception when calling Request.GetOwinContext()) you'll need to install the Microsoft.AspNet.WebApi.Owin NuGet package within your test project. Once that is installed you can use the SetOwinContext extension method on the request.

Example:

var controller = new MyController();
controller.Request = new HttpRequestMessage(HttpMethod.Post,
    new Uri("api/data/validate", UriKind.Relative)
    );
controller.Request.SetOwinContext(new OwinContext());

See https://msdn.microsoft.com/en-us/library/system.net.http.owinhttprequestmessageextensions.setowincontext%28v=vs.118%29.aspx

That being said, I agree with the other answers for your specific use case -- provide an AppplicationUserManager instance or factory in the constructor. The SetOwinContext steps above are necessary if you need to directly interact with the context your test will use.

like image 20
Gnosian Avatar answered Oct 31 '22 18:10

Gnosian


You can just pass in the UserManager in the constructor of the AccountController, so it doesn't try to find it in the owinContext. The default constructor is not unit test friendly.

like image 29
Hao Kung Avatar answered Oct 31 '22 17:10

Hao Kung


What I tend to do is to inject AccountController with a user manager factory. That way you can easily swap an instance of the user manager that is used in test. Your default factory can take the request in the constructor to continue providing per request instances of the user manager. Your test factory simply returns an instance of the user manager you want to supply your tests with, I usually go for one that takes a stubbed out instance of a IUserStore so there is no hard dependency on a back end used for storing identity information.

Factory interface and class:

public interface IUserManagerFactory<TUser>
    where TUser : class, global::Microsoft.AspNet.Identity.IUser<string>
{
    UserManager<TUser> Create();
}


public class UserManagerFactory : IUserManagerFactory<AppUser>
{
    private HttpRequestMessage request;

    public UserManagerFactory(HttpRequestMessage request)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        this.request = request;
    }

    public UserManager<AppUser, string> Create()
    {
        return request.GetOwinContext().GetUserManager<UserManager<AppUser>>();
    }
}

AccountController:

public AccountController(IUserManagerFactory<AppUser> userManagerFactory)
{
    this.userManagerFactory = userManagerFactory;
}

private UserManager<AppUser> userManager;

public UserManager<AppUser> UserManager
{
    get
    {
        if (this.userManager == null)
        {
            this.userManager = this.userManagerFactory.Create(); 
        }

        return this.userManager;
    }
}

Test factory:

public class TestUserManagerFactory : IUserManagerFactory<AppUser>
{
    private IUserStore<AppUser> userStore;

    public TestUserManagerFactory()
    {
        this.userStore = new MockUserStore();
    }

    public UserManager<AppUser> Create()
    { 
        return new UserManager<AppUser>(new MockUserStore());
    }
}
like image 1
tstojecki Avatar answered Oct 31 '22 18:10

tstojecki