it-swarm.com.de

Request.GetOwinContext gibt im Komponententest null zurück - wie teste ich die OWIN-Authentifizierung im Komponententest?

Ich versuche derzeit, die Authentifizierung für ein neues WebAPI-Projekt, das ich mit OWIN zur Authentifizierung schreibe, einem Komponententest zu unterziehen, und habe Probleme, es in einem Komponententestkontext auszuführen.

Dies ist meine Testmethode:

[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 = "[email protected]",
            ConfirmPassword = "[email protected]"
        }).Result;
        Assert.IsNotNull(result);
    }
}

Ich erhalte eine AggregateException, wenn ich den .Result erhalte, mit der folgenden inneren Ausnahme:

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

Ich habe über das Debuggen bestätigt, dass meine Startup-Methode aufgerufen wird und ConfigurAuth aufruft:

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

Ich habe ein paar Dinge ausprobiert, aber nichts scheint zu funktionieren - ich kann niemals einen OWIN-Kontext erhalten. Der Test schlägt mit dem folgenden Code fehl:

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

Dies ruft die Eigenschaft UserManager auf:

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

Es schlägt fehl am:

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

mit einer NullReferenceException - Request.GetOwinContext gibt null zurück.

Meine Frage lautet also: gehe ich das falsch an? Sollte ich nur die JSON-Antworten testen? Oder gibt es eine gute Möglichkeit, die OWIN-Authentifizierung "intern" zu testen?

18
Codeman

GetOwinContext ruft context.GetOwinEnvironment (); Auf

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

und HttpContextItemKeys.OwinEnvironmentKey ist eine Konstante "owin.Environment" Wenn Sie also das Element in Ihrem httpcontext-Element hinzufügen, wird es funktionieren. 

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

Um sicherzustellen, dass während des Tests ein OWIN-Kontext verfügbar ist (d. H., Um die NULL-Referenzausnahme beim Aufruf von Request.GetOwinContext() zu beheben), müssen Sie das Microsoft.AspNet.WebApi.Owin-NuGet-Paket in Ihrem Testprojekt installieren. Sobald dies installiert ist, können Sie die Erweiterungsmethode SetOwinContext für die Anforderung verwenden.

Beispiel:

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

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

Davon abgesehen stimme ich den anderen Antworten für Ihren speziellen Anwendungsfall zu. Geben Sie eine AppplicationUserManager-Instanz oder -Factory im Konstruktor an. Die oben genannten SetOwinContext-Schritte sind erforderlich, wenn Sie direkt mit dem Kontext interagieren müssen, den Ihr Test verwendet.

9
Gnosian

Sie können den UserManager einfach im Konstruktor des AccountControllers übergeben, damit er nicht im owinContext gefunden wird. Der Standardkonstruktor ist nicht für Komponententests geeignet.

2
Hao Kung

Ich neige dazu, AccountController mit einer Benutzermanagerfabrik zu injizieren. Auf diese Weise können Sie leicht eine Instanz des Benutzermanagers austauschen, der im Test verwendet wird. Ihre Standardfactory kann die Anforderung im Konstruktor übernehmen, um weiterhin pro Anforderung Instanzen des Benutzermanagers bereitzustellen. Ihre Testfactory gibt einfach eine Instanz des Benutzermanagers zurück, mit dem Sie Ihre Tests bereitstellen möchten. Normalerweise gehe ich für eine Instanz, die eine überfüllte Instanz eines IUserStore benötigt, sodass keine harte Abhängigkeit von einem Back-End besteht, das zum Speichern von Identitätsinformationen verwendet wird.

Factory Interface und Klasse:

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

Testfabrik:

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;

Verwendete dieses Stück Code und fügte HttpContext anstelle von ctx hinzu, und der Komponententest funktionierte wie ein Zauber.

1

Die Antworten hier haben geholfen, aber mich nicht ganz dahin gebracht, hier ein vollständiges Beispiel:

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;

Denken Sie daran, alle erforderlichen Nuget-Pakete zu installieren!

0
Shahin Dohan