C# OWIN网络API+;OWIN测试服务器+;Ninject依赖项注入
从高层角度来看,我想使用OWIN测试服务器对我创建的Web API控制器执行集成测试。我大致上遵循了本教程。下面是技术堆栈概述 OWINC# OWIN网络API+;OWIN测试服务器+;Ninject依赖项注入,c#,testing,asp.net-web-api2,ninject,owin,C#,Testing,Asp.net Web Api2,Ninject,Owin,从高层角度来看,我想使用OWIN测试服务器对我创建的Web API控制器执行集成测试。我大致上遵循了本教程。下面是技术堆栈概述 OWIN Microsoft.Owin.Host.SystemWeb Microsoft.OWIN.Testing 用于自托管的其他Owin组件 Ninject public partial class Startup { #region Ninject Dependency Injection private static IKernel _con
- Microsoft.Owin.Host.SystemWeb
- Microsoft.OWIN.Testing
- 用于自托管的其他Owin组件
public partial class Startup
{
#region Ninject Dependency Injection
private static IKernel _container { get; set; }
public static IKernel Container
{
get
{
if (UseUnitTestKernel)
{
return _container;
}
throw new Exception("Startup has not been designated to use integration test dependeny injection");
}
}
public static bool UseUnitTestKernel { get; set; }
#endregion Ninject Dependency Injection
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
//Save off _app builder in case we want to retrieve a value later
private static IAppBuilder _app;
/// <summary>
/// Bootstrap application
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
_app = app;
ConfigureAuth(app);
if (!UseUnitTestKernel)
{
app.UseNinjectMiddleware(NinjectWebApiKernel.CreateKernel);
app.UseNinjectWebApi(ConfigWebAPI());
}
else
{
app.UseWebApi(ConfigWebAPIForIntegrationTesting());
}
}
/// <summary>
/// Configure authorization
/// </summary>
/// <param name="app"></param>
public void ConfigureAuth(IAppBuilder app)
{
PublicClientId = "JustBlogWeb";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(20),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
}
/// <summary>
/// Setup WebAPI routes
/// </summary>
/// <returns></returns>
private HttpConfiguration ConfigWebAPI()
{
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
//Must have this on to view errors;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnableSwagger(c => c.SingleApiVersion("v1", "Just Blog API")).EnableSwaggerUi();
return config;
}
/// <summary>
/// Configure Web API for integration testing
/// </summary>
/// <returns></returns>
private HttpConfiguration ConfigWebAPIForIntegrationTesting()
{
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
//Must have this on to view errors;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
_container = new StandardKernel();
_container.Bind<DefaultModelValidatorProviders>()
.ToConstant(new DefaultModelValidatorProviders(
config.Services.GetServices(typeof(ModelValidatorProvider)).Cast<ModelValidatorProvider>()));
_container.Bind<DefaultFilterProviders>()
.ToConstant(new DefaultFilterProviders(
config.Services.GetServices(typeof(IFilterProvider)).Cast<DefaultFilterProvider>()));
//_container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindAllInterfaces());
_container.Bind<IPostService>().To<PostService>();
config.DependencyResolver = new NinjectUnitTestDependencyResolver(_container);
return config;
}
/// <summary>
/// Static class for generating a Web API Ninject Kernel
/// </summary>
public static class NinjectWebApiKernel
{
//List of Mocked Services
public static IDictionary<Type, Mock> Mocks;
/// <summary>
/// Create kernel bindings for IoC and Dependency Injection
/// </summary>
/// <returns></returns>
public static IKernel CreateKernel()
{
IKernel kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
RegisterServices(kernel);
//Rebind mocked services if we have any
// TODO Remove rebind and extraneous ninject rules
//RebindToConfiguredMocks(_container, Mocks);
return kernel;
}
/// <summary>
/// Register ninject services
/// </summary>
/// <param name="kernel"></param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IAuthProvider>().To<AuthProvider>();
kernel.Bind<IJustBlogContext>().To<JustBlogContext>();
kernel.Bind<IPostService>().To<PostService>();
kernel.Bind<ICategoryService>().To<CategoryService>();
kernel.Bind<ITagService>().To<TagService>();
}
/// <summary>
/// Rebind all mocked services for dependency injection
/// </summary>
private static void RebindToConfiguredMocks(IKernel kernel, IDictionary<Type, Mock> mocks)
{
foreach (var item in mocks)
{
kernel.Unbind(item.Key);
var item1 = item;
kernel.Bind(item.Key).ToMethod(_ => mocks[item1.Key].Object);
}
}
}
/// <summary>
/// Ninject Unit Testing Dependency resolver. We use this dependency resolver
/// for unit testing
/// </summary>
public class NinjectUnitTestDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
{
protected IResolutionRoot Container;
public NinjectUnitTestDependencyResolver(IKernel container)
{
Container = container;
}
public NinjectUnitTestDependencyResolver(IResolutionRoot container)
{
Container = container;
}
public object GetService(Type serviceType)
{
return Container.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return Container.GetAll(serviceType);
}
public IDependencyScope BeginScope()
{
if (!(Container is IKernel))
{
throw new NotImplementedException("Can't begin a scope on a scope");
}
return new NinjectUnitTestDependencyResolver(((IKernel)Container).BeginBlock());
}
public void Dispose()
{
}
}
public class PostController_Tests
{
private TestServer _server;
private const string _baseServerURL = "http://testserver";
private IDictionary<Type, Mock> _mocks;
/// <summary>
/// Integration test for the Post Controller "UnitTest" endpoint
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task PostController_UnitTest_Test()
{
string endPoint = $"{_baseServerURL}/{RoutePrefixes.RESOURCE_POST}/{nameof(PostController.UnitTest)}";
HttpResponseMessage response = await _server.HttpClient.GetAsync(endPoint);
string content = await response.Content.ReadAsStringAsync();
Assert.AreEqual(content, "true");
}
/// <summary>
/// Test the retrieval of a post by url tag
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task RetrievePost_By_UrlTag_Test()
{
string endPoint = GenerateAPIEndpoint(RoutePrefixes.RESOURCE_POST, nameof(PostController.Retrieve));
var postSet_Mock = new Mock<DbSet<Post>>();
var justBlogContext_Mock = new Mock<JustBlogContext>();
justBlogContext_Mock.Setup(m => m.Posts).Returns(postSet_Mock.Object);
IPostService postService = new PostService(justBlogContext_Mock.Object);
MockOut<IPostService>().Setup(x => x.Post(It.IsAny<string>())).Returns(new PostViewModel());
HttpResponseMessage response = await _server.HttpClient.GetAsync(endPoint);
PostViewModel result = await response.Content.ReadAsAsync<PostViewModel>();
}
/// <summary>
/// Run after the entire test class runs
/// </summary>
[TestInitialize()]
public void ServerInit()
{
_mocks = new ConcurrentDictionary<Type, Mock>();
Startup.UseUnitTestKernel = true;
_server = TestServer.Create<Startup>();
var test = Startup.GetTestKernel();
}
/// <summary>
/// Run before each class is initialized
/// </summary>
/// <param name="context"></param>
[TestCleanup()]
public void ServerDispose()
{
_server.Dispose();
}
#region Private Methods and Classes
/// <summary>
/// Generate the API endpoint for a controller method
/// </summary>
/// <param name="controllerName"></param>
/// <param name="methodName"></param>
/// <returns></returns>
protected string GenerateAPIEndpoint(string controllerName, string methodName)
{
return $"{_baseServerURL}/{controllerName}/{methodName}";
}
/// <summary>
/// Create a new mocked service
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected Mock<T> MockOut<T>() where T :class
{
if (_mocks.ContainsKey(typeof(T)))
{
RebindToConfiguredMocks();
return (Mock<T>)_mocks[typeof(T)];
}
_mocks.Add(typeof(T), new Mock<T>());
RebindToConfiguredMocks();
return (Mock<T>)_mocks[typeof(T)];
}
/// <summary>
/// Rebind all mocked services for dependency injection
/// </summary>
protected void RebindToConfiguredMocks()
{
foreach(var item in _mocks)
{
Startup.Container.Unbind(item.Key);
var item1 = item;
Startup.Container.Bind(item.Key).ToMethod(_ => _mocks[item1.Key].Object);
}
}
#endregion Private Methods and Classes
}
- Ninject.Web.WebAPI.OwinHost
- 其他相关的Ninject Web API组件
- 用于模拟控制器中的服务类
public partial class Startup
{
#region Ninject Dependency Injection
private static IKernel _container { get; set; }
public static IKernel Container
{
get
{
if (UseUnitTestKernel)
{
return _container;
}
throw new Exception("Startup has not been designated to use integration test dependeny injection");
}
}
public static bool UseUnitTestKernel { get; set; }
#endregion Ninject Dependency Injection
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
//Save off _app builder in case we want to retrieve a value later
private static IAppBuilder _app;
/// <summary>
/// Bootstrap application
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
_app = app;
ConfigureAuth(app);
if (!UseUnitTestKernel)
{
app.UseNinjectMiddleware(NinjectWebApiKernel.CreateKernel);
app.UseNinjectWebApi(ConfigWebAPI());
}
else
{
app.UseWebApi(ConfigWebAPIForIntegrationTesting());
}
}
/// <summary>
/// Configure authorization
/// </summary>
/// <param name="app"></param>
public void ConfigureAuth(IAppBuilder app)
{
PublicClientId = "JustBlogWeb";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(20),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
}
/// <summary>
/// Setup WebAPI routes
/// </summary>
/// <returns></returns>
private HttpConfiguration ConfigWebAPI()
{
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
//Must have this on to view errors;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnableSwagger(c => c.SingleApiVersion("v1", "Just Blog API")).EnableSwaggerUi();
return config;
}
/// <summary>
/// Configure Web API for integration testing
/// </summary>
/// <returns></returns>
private HttpConfiguration ConfigWebAPIForIntegrationTesting()
{
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
//Must have this on to view errors;
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
_container = new StandardKernel();
_container.Bind<DefaultModelValidatorProviders>()
.ToConstant(new DefaultModelValidatorProviders(
config.Services.GetServices(typeof(ModelValidatorProvider)).Cast<ModelValidatorProvider>()));
_container.Bind<DefaultFilterProviders>()
.ToConstant(new DefaultFilterProviders(
config.Services.GetServices(typeof(IFilterProvider)).Cast<DefaultFilterProvider>()));
//_container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindAllInterfaces());
_container.Bind<IPostService>().To<PostService>();
config.DependencyResolver = new NinjectUnitTestDependencyResolver(_container);
return config;
}
/// <summary>
/// Static class for generating a Web API Ninject Kernel
/// </summary>
public static class NinjectWebApiKernel
{
//List of Mocked Services
public static IDictionary<Type, Mock> Mocks;
/// <summary>
/// Create kernel bindings for IoC and Dependency Injection
/// </summary>
/// <returns></returns>
public static IKernel CreateKernel()
{
IKernel kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
RegisterServices(kernel);
//Rebind mocked services if we have any
// TODO Remove rebind and extraneous ninject rules
//RebindToConfiguredMocks(_container, Mocks);
return kernel;
}
/// <summary>
/// Register ninject services
/// </summary>
/// <param name="kernel"></param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IAuthProvider>().To<AuthProvider>();
kernel.Bind<IJustBlogContext>().To<JustBlogContext>();
kernel.Bind<IPostService>().To<PostService>();
kernel.Bind<ICategoryService>().To<CategoryService>();
kernel.Bind<ITagService>().To<TagService>();
}
/// <summary>
/// Rebind all mocked services for dependency injection
/// </summary>
private static void RebindToConfiguredMocks(IKernel kernel, IDictionary<Type, Mock> mocks)
{
foreach (var item in mocks)
{
kernel.Unbind(item.Key);
var item1 = item;
kernel.Bind(item.Key).ToMethod(_ => mocks[item1.Key].Object);
}
}
}
/// <summary>
/// Ninject Unit Testing Dependency resolver. We use this dependency resolver
/// for unit testing
/// </summary>
public class NinjectUnitTestDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
{
protected IResolutionRoot Container;
public NinjectUnitTestDependencyResolver(IKernel container)
{
Container = container;
}
public NinjectUnitTestDependencyResolver(IResolutionRoot container)
{
Container = container;
}
public object GetService(Type serviceType)
{
return Container.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return Container.GetAll(serviceType);
}
public IDependencyScope BeginScope()
{
if (!(Container is IKernel))
{
throw new NotImplementedException("Can't begin a scope on a scope");
}
return new NinjectUnitTestDependencyResolver(((IKernel)Container).BeginBlock());
}
public void Dispose()
{
}
}
public class PostController_Tests
{
private TestServer _server;
private const string _baseServerURL = "http://testserver";
private IDictionary<Type, Mock> _mocks;
/// <summary>
/// Integration test for the Post Controller "UnitTest" endpoint
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task PostController_UnitTest_Test()
{
string endPoint = $"{_baseServerURL}/{RoutePrefixes.RESOURCE_POST}/{nameof(PostController.UnitTest)}";
HttpResponseMessage response = await _server.HttpClient.GetAsync(endPoint);
string content = await response.Content.ReadAsStringAsync();
Assert.AreEqual(content, "true");
}
/// <summary>
/// Test the retrieval of a post by url tag
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task RetrievePost_By_UrlTag_Test()
{
string endPoint = GenerateAPIEndpoint(RoutePrefixes.RESOURCE_POST, nameof(PostController.Retrieve));
var postSet_Mock = new Mock<DbSet<Post>>();
var justBlogContext_Mock = new Mock<JustBlogContext>();
justBlogContext_Mock.Setup(m => m.Posts).Returns(postSet_Mock.Object);
IPostService postService = new PostService(justBlogContext_Mock.Object);
MockOut<IPostService>().Setup(x => x.Post(It.IsAny<string>())).Returns(new PostViewModel());
HttpResponseMessage response = await _server.HttpClient.GetAsync(endPoint);
PostViewModel result = await response.Content.ReadAsAsync<PostViewModel>();
}
/// <summary>
/// Run after the entire test class runs
/// </summary>
[TestInitialize()]
public void ServerInit()
{
_mocks = new ConcurrentDictionary<Type, Mock>();
Startup.UseUnitTestKernel = true;
_server = TestServer.Create<Startup>();
var test = Startup.GetTestKernel();
}
/// <summary>
/// Run before each class is initialized
/// </summary>
/// <param name="context"></param>
[TestCleanup()]
public void ServerDispose()
{
_server.Dispose();
}
#region Private Methods and Classes
/// <summary>
/// Generate the API endpoint for a controller method
/// </summary>
/// <param name="controllerName"></param>
/// <param name="methodName"></param>
/// <returns></returns>
protected string GenerateAPIEndpoint(string controllerName, string methodName)
{
return $"{_baseServerURL}/{controllerName}/{methodName}";
}
/// <summary>
/// Create a new mocked service
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected Mock<T> MockOut<T>() where T :class
{
if (_mocks.ContainsKey(typeof(T)))
{
RebindToConfiguredMocks();
return (Mock<T>)_mocks[typeof(T)];
}
_mocks.Add(typeof(T), new Mock<T>());
RebindToConfiguredMocks();
return (Mock<T>)_mocks[typeof(T)];
}
/// <summary>
/// Rebind all mocked services for dependency injection
/// </summary>
protected void RebindToConfiguredMocks()
{
foreach(var item in _mocks)
{
Startup.Container.Unbind(item.Key);
var item1 = item;
Startup.Container.Bind(item.Key).ToMethod(_ => _mocks[item1.Key].Object);
}
}
#endregion Private Methods and Classes
}
公共部分类启动
{
#区域对象依赖注入
私有静态IKernel_容器{get;set;}
公共静态IKernel容器
{
得到
{
if(UseUnitTestKernel)
{
返回容器;
}
抛出新异常(“启动未被指定为使用集成测试依赖注入”);
}
}
公共静态bool UseUnitTestKernel{get;set;}
#endregion对象依赖项注入
公共静态OAuthAuthorizationServerOptions OAuthOptions{get;private set;}
公共静态字符串PublicClientId{get;private set;}
//保存应用程序生成器,以防以后要检索值
私有静态IAppBuilder(应用程序);
///
///引导应用程序
///
///
公共无效配置(IAppBuilder应用程序)
{
_app=app;
ConfigureAuth(app);
如果(!UseUnitTestKernel)
{
app.usenjectMiddleware(ninjectwebapickernel.CreateKernel);
app.UseNinjectWebApi(ConfigWebAPI());
}
其他的
{
app.UseWebApi(ConfigWebAPIForIntegrationTesting());
}
}
///
///配置授权
///
///
public void ConfigureAuth(IAppBuilder应用程序)
{
PublicClientId=“JustBlogWeb”;
OAuthOptions=新的OAuthAuthorizationServerOptions
{
TokenEndpointPath=新路径字符串(“/Token”),
AuthorizeEndpointPath=新路径字符串(“/Account/Authorize”),
Provider=新的ApplicationAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan=TimeSpan.FromDays(14),
AllowInsecureHttp=true
};
//使应用程序能够使用cookie存储登录用户的信息
app.UseCookieAuthentication(新的CookieAuthenticationOptions
{
AuthenticationType=DefaultAuthenticationTypes.ApplicationOkie,
LoginPath=新路径字符串(“/Account/Login”),
Provider=新CookieAuthenticationProvider
{
//允许应用程序在用户登录时验证安全戳。
//这是一种安全功能,在您更改密码或向帐户添加外部登录时使用。
OnValidateIdentity=SecurityStampValidator.OnValidateIdentity(
validateInterval:TimeSpan.FromMinutes(20),
regenerateIdentity:(管理器,用户)=>user.GenerateUserIdentityAsync(管理器))
}
});
//将数据库上下文、用户管理器和登录管理器配置为每个请求使用一个实例
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext(ApplicationUserManager.Create);
app.CreatePerOwinContext(ApplicationSignInManager.Create);
}
///
///设置WebAPI路由
///
///
私有HttpConfiguration ConfigWebAPI()
{
HttpConfiguration config=新的HttpConfiguration();
//Web API配置和服务
//必须打开此选项才能查看错误;
config.IncludeErrorDetailPolicy=IncludeErrorDetailPolicy.Always;
//Web API路由
config.maphttpAttribute路由();
config.Routes.MapHttpRoute(
名称:“DefaultApi”,
routeTemplate:“api/{controller}/{id}”,
默认值:新建{id=RouteParameter.Optional}
);
config.EnableSwagger(c=>c.SingleApiVersion(“v1”,“Just Blog API”)).EnableSwaggerUi();
返回配置;
}
///
///为集成测试配置Web API
///
///
私有HttpConfiguration ConfigWebAPIForIntegrationTesting()
{
HttpConfiguration config=新的HttpConfiguration();
//Web API配置和服务
//必须打开此选项才能查看错误;
config.Includ