Reputation: 1050
I am using Autofac and OWIN in a WebAPI project which was build from scratch (as apposed to the complete WebAPI template available in VS2015). Admittedly I am new to doing it this way.
In the unit test project, I set up an OWIN Startup class at the start of unit testing:
WebApp.Start<Startup>("http://localhost:9000/")
The Startup class is as follows:
[assembly: OwinStartup(typeof(API.Specs.Startup))]
namespace API.Specs
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
//config.Filters.Add(new AccessControlAttribute());
config.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());
config.Formatters.JsonFormatter.SerializerSettings = Serializer.Settings;
config.MapHttpAttributeRoutes();
// Autofac configuration
var builder = new ContainerBuilder();
// Unit of Work
var unitOfWork = new Mock<IUnitOfWork>();
builder.RegisterInstance(unitOfWork.Object).As<IUnitOfWork>();
// Principal
var principal = new Mock<IPrincipal>();
principal.Setup(p => p.IsInRole("admin")).Returns(true);
principal.SetupGet(p => p.Identity.Name).Returns('test.user');
principal.SetupGet(p => p.Identity.IsAuthenticated).Returns(true);
Thread.CurrentPrincipal = principal.Object;
if (HttpContext.Current != null)
{
HttpContext.Current.User = new GenericPrincipal(principal.Object.Identity, null);
}
builder.Register(c => principal).As<IPrincipal>();
.
.
.
// Set up dependencies for Controllers, Services & Repositories
.
.
.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
appBuilder.UseWebApi(config);
}
private static void RegisterAssemblies<TModel, TController, TService, TRepoClass, TRepoInterface>(ref ContainerBuilder builder, ref Mock<IUnitOfWork> unitOfWork)
where TModel : class
where TRepoClass : class
where TService : class
{
RegisterController<TController>(ref builder);
var repositoryInstance = RegisterRepository<TRepoClass, TRepoInterface>(ref builder);
RegisterService<TService>(ref builder, ref unitOfWork, repositoryInstance);
}
private static void RegisterController<TController>(ref ContainerBuilder builder)
{
builder.RegisterApiControllers(typeof(TController).Assembly);
}
private static object RegisterRepository<TRepoClass, TRepoInterface>(ref ContainerBuilder builder)
where TRepoClass : class
{
var constructorArguments = new object[] { DataContexts.Instantiate };
var repositoryInstance = Activator.CreateInstance(typeof(TRepoClass), constructorArguments);
builder.RegisterInstance(repositoryInstance).As<TRepoInterface>();
return repositoryInstance;
}
private static void RegisterService<TService>(ref ContainerBuilder builder, ref Mock<IUnitOfWork> unitOfWork, object repositoryInstance)
where TService : class
{
var constructorArguments = new[] { repositoryInstance, unitOfWork.Object};
var serviceInstance = Activator.CreateInstance(typeof(TService), constructorArguments);
builder.RegisterAssemblyTypes(typeof(TService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();
builder.RegisterInstance(serviceInstance);
}
}
}
Side note: Ideally I would like to set the Principle as part of the test, so as to be able to pass different users to the controller, but if I absolutely have to keep the setting of the CurrentPrincipal/User in the startup class, I can work around it.
The startup class works fine w.r.t accessing my controllers using DI, however the Principal in RequestContext.Principal
is never set. It is always null. The way I intent to use the Request context is as follows:
[HttpGet]
[Route("path/{testId}")]
[ResponseType(typeof(Test))]
public IHttpActionResult Get(string testId)
{
return Ok(_service.GetById(testId, RequestContext.Principal.Identity.Name));
}
I have also tried injecting the mocked principal class into the constructor of my Controller as a workaround - I used the same method as displayed in the generic method for setting up my services using DI. Again however, I only got null in my constructor.
At this point I have been sitting for about a day with this problem and pulling out my hair. Any help would be appreciated. Thanks in advance.
Upvotes: 1
Views: 818
Reputation: 23894
I'd avoid doing this with DI. You need something to set the principal in the request context, not inject the principal into a constructor.
Here's what I'd do if it was me:
First, I won't mock things that don't need mocking. Which is to say, your IIdentity
implementation could actually be real objects.
private static IPrincipal CreatePrincipal()
{
var identity = new GenericIdentity("test.user", "test");
var roles = new string[] { "admin" };
return new GenericPrincipal(identity);
}
Next, you need to run the setup on each "request" you process through your test app. I'm guessing this is more "integration test" than "unit test" since you're using a whole startup class and everything, so you can't just set the principal one time and be done. It has to be done on every request, just like a real authentication action would do.
The easiest way to do that is with a simple delegating handler.
public class TestAuthHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Set the principal. Whether you set the thread principal
// is optional but you should really use the request context
// principal exclusively when checking permissions.
request.GetRequestContext().Principal = CreatePrincipal();
// Let the request proceed through the rest of the pipeline.
return await base.SendAsync(request, cancellationToken);
}
}
Finally, add that handler to the HttpConfiguration
pipeline in your Startup
class.
config.MessageHandlers.Add(new TestAuthHandler());
That should do it. Requests should now go through that auth handler and get the principal assigned.
Upvotes: 2