web-dev-qa-db-fra.com

Request.GetOwinContext renvoie la valeur null dans le test d'unité. Comment tester l'authentification OWIN dans un test d'unité?

J'essaie actuellement de tester l'authentification unitaire pour un nouveau projet WebAPI que j'écris en utilisant OWIN pour m'authentifier et j'ai des problèmes pour l'exécuter dans un contexte de test unitaire.

Ceci est ma méthode de test:

[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);
    }
}

Je reçois une AggregateException après avoir obtenu le .Result avec l'exception interne suivante:

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()
...

J'ai confirmé via le débogage que ma méthode Startup est appelée, en appelant 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);
}

J'ai essayé plusieurs choses, mais rien ne semble fonctionner - je ne peux jamais obtenir un contexte OWIN. Le test échoue sur le code suivant:

// 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();
}

Ceci appelle la propriété UserManager:

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

Il échoue sur:

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

avec un NullReferenceException - Request.GetOwinContext renvoie null.

Ma question est donc la suivante: est-ce que je me trompe? Devrais-je simplement tester les réponses JSON? Ou existe-t-il un bon moyen de tester "en interne" l'authentification OWIN?

18
Codeman

GetOwinContext appelle context.GetOwinEnvironment (); Qui est

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

et HttpContextItemKeys.OwinEnvironmentKey est une constante "owin.Environment" Si vous ajoutez cela dans les éléments de votre httpcontext, cela fonctionnera. 

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;
11
Birey

Pour vous assurer qu'un contexte OWIN est disponible pendant votre test (c'est-à-dire pour corriger l'exception de référence nulle lorsque vous appelez Request.GetOwinContext()), vous devez installer le package Microsoft.AspNet.WebApi.Owin NuGet dans votre projet de test. Une fois installé, vous pouvez utiliser la méthode d’extension SetOwinContext sur la demande.

Exemple:

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

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

Cela étant dit, je suis d'accord avec les autres réponses pour votre cas d'utilisation spécifique - fournissez une instance ou une fabrique AppplicationUserManager dans le constructeur. Les étapes SetOwinContext ci-dessus sont nécessaires si vous devez interagir directement avec le contexte que votre test utilisera.

9
Gnosian

Vous pouvez simplement passer UserManager dans le constructeur de AccountController afin qu'il ne tente pas de le trouver dans owinContext. Le constructeur par défaut n'est pas convivial aux tests unitaires.

2
Hao Kung

Ce que j'ai tendance à faire, c'est d'injecter AccountController avec une usine de gestion des utilisateurs. De cette façon, vous pouvez facilement échanger une instance du gestionnaire d'utilisateurs utilisée dans test. Votre usine par défaut peut utiliser la demande du constructeur pour continuer à fournir des instances par requête du gestionnaire d'utilisateurs. Votre fabrique de tests renvoie simplement une instance du gestionnaire d'utilisateurs avec lequel vous souhaitez fournir vos tests. J'utilise généralement une instance qui prend une instance tronquée d'un IUserStore, de sorte qu'il n'y a pas de dépendance ferme à un serveur principal utilisé pour stocker les informations d'identité.

Interface d'usine et classe:

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;
    }
}

Usine de test:

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());
    }
}
1
Tom
var data = new Dictionary<string, object>()
{
    {"a", "b"} // fake whatever  you need here.
};

ctx.Items["owin.Environment"] = data;

Utilisé ce morceau de code et ajouté à HttpContext au lieu de ctx et le test unitaire a fonctionné comme un charme.

1
Santiago Rebella

Les réponses ici ont aidé, mais ne m'ont pas tout à fait compris, voici un exemple complet:

var userStore = new Mock<IUserStore<User>>();
var appUserMgrMock = new Mock<ApplicationUserManager>(userStore.Object);

var owin = new OwinContext();
owin.Set(appUserMgrMock.Object);

HttpContext.Current = new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null));
HttpContext.Current.Items["owin.Environment"] = owin.Environment;

N'oubliez pas d'installer tous les paquets de nuget requis!

0
Shahin Dohan