asp.net mvc - unitarias - Request.GetOwinContext devuelve un valor nulo dentro de la prueba unitaria. ¿Cómo puedo probar la autenticación OWIN dentro de una prueba unitaria?
pruebas unitarias c# visual studio 2017 (6)
GetOwinContext llama a context.GetOwinEnvironment (); cual es
private static IDictionary<string, object> GetOwinEnvironment(this HttpContextBase context)
{
return (IDictionary<string, object>) context.Items[HttpContextItemKeys.OwinEnvironmentKey];
}
y HttpContextItemKeys.OwinEnvironmentKey es un "owin.Environment" constante. Por lo tanto, si agrega que en los elementos de su httpcontext, funcionará.
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;
Actualmente estoy tratando de realizar la prueba unitaria de la autenticación para un nuevo proyecto WebAPI que estoy escribiendo usando OWIN para autenticar, y tengo problemas para ejecutarlo en un contexto de prueba unitaria.
Este es mi método de prueba:
[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);
}
}
Recibo una excepción AggregateException
para obtener el .Result
con la siguiente excepción interna:
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()
...
Confirmé a través de la depuración que se está llamando a mi método de Startup
, llamando a 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);
}
He intentado algunas cosas, pero nada parece funcionar, nunca puedo obtener un contexto OWIN. La prueba está fallando en el siguiente código:
// 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();
}
Esto llama a la propiedad UserManager
:
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
Falla en:
return _userManager ?? Request.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
con una NullReferenceException
: Request.GetOwinContext
devuelve un null
.
Así que mi pregunta es: ¿Me estoy acercando a esto mal? ¿Debería estar probando las respuestas JSON? ¿O hay una buena manera de probar "internamente" la autenticación OWIN?
Las respuestas aquí ayudaron, pero no me ayudaron a llegar hasta el final, aquí hay un ejemplo completo:
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;
¡Recuerde instalar todos los paquetes de nuget requeridos!
Lo que tiendo a hacer es inyectar AccountController con un administrador de usuarios de fábrica. De esa manera, puede intercambiar fácilmente una instancia del administrador de usuarios que se utiliza en la prueba. Su fábrica predeterminada puede tomar la solicitud en el constructor para continuar proporcionando instancias por solicitud del administrador de usuarios. Su fábrica de pruebas simplemente devuelve una instancia del administrador de usuarios con el que desea suministrar sus pruebas. Por lo general, busco una que elimine la instancia de un IUserStore, por lo que no hay una dependencia fuerte en un back-end que se usa para almacenar información de identidad.
Interfaz de fábrica y clase:
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;
}
}
Prueba de fábrica:
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());
}
}
Para asegurarse de que haya un contexto OWIN disponible durante su prueba (es decir, para corregir la excepción de referencia nula al llamar a Request.GetOwinContext()
) necesitará instalar el paquete Microsoft.AspNet.WebApi.Owin
NuGet dentro de su proyecto de prueba. Una vez que esté instalado, puede usar el método de extensión SetOwinContext
en la solicitud.
Ejemplo:
var controller = new MyController();
controller.Request = new HttpRequestMessage(HttpMethod.Post,
new Uri("api/data/validate", UriKind.Relative)
);
controller.Request.SetOwinContext(new OwinContext());
Dicho esto, estoy de acuerdo con las otras respuestas para su caso de uso específico: proporcione una instancia o fábrica de AppplicationUserManager en el constructor. Los pasos de SetOwinContext
anteriores son necesarios si necesita interactuar directamente con el contexto que usará su prueba.
Solo puede pasar el UserManager en el constructor del AccountController, para que no intente encontrarlo en el owinContext. El constructor predeterminado no es compatible con la prueba de unidad.
var data = new Dictionary<string, object>()
{
{"a", "b"} // fake whatever you need here.
};
ctx.Items["owin.Environment"] = data;
Usé este fragmento de código y lo agregué al HttpContext en lugar de ctx y la prueba de la unidad funcionó a la perfección.