Asp.net WebAPI 单元测试
现在Asp.net webapi 运用的越来越多,其单元而是也越来越重要。一般软件开发都是多层结构,上层调用下层的接口,而各层的实现人员不同,一般大家都只写自己对应单元测试。对下层的依赖我们通过IOC来做。首先看我们的Controller定义及实现
public class ArticlesController : ApiController { private IArticleService _articleService; public ArticlesController(IArticleService articleService) { _articleService = articleService; } // GET: api/Articles public IEnumerable<Article> GetArticles() { return _articleService.GetArticles(); } // GET: api/Articles/5 [ResponseType(typeof(Article))] public IHttpActionResult GetArticle(int id) { Article article = _articleService.GetArticle(id); if (article == null) { return NotFound(); } return Ok(article); } // PUT: api/Articles/5 [ResponseType(typeof(void))] public IHttpActionResult PutArticle(int id, Article article) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (id != article.ID) { return BadRequest(); } try { _articleService.UpdateArticle(article); } catch (DbUpdateConcurrencyException) { if (!ArticleExists(id)) { return NotFound(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } // POST: api/Articles [ResponseType(typeof(Article))] public IHttpActionResult PostArticle(Article article) { if (!ModelState.IsValid) { return BadRequest(ModelState); } _articleService.CreateArticle(article); return CreatedAtRoute("DefaultApi", new { id = article.ID }, article); } // DELETE: api/Articles/5 [ResponseType(typeof(Article))] public IHttpActionResult DeleteArticle(int id) { Article article = _articleService.GetArticle(id); if (article == null) { return NotFound(); } _articleService.DeleteArticle(article); return Ok(article); } private bool ArticleExists(int id) { return _articleService.GetArticle(id) != null; } }
首先构造函数 需要IArticleService实例,在Controller的action中将需要用到该service。【请千万不要告诉我说这里的controller非常简单,不需要做单元测试!在实际项目中有人为了不做Controller的单元测试,把里面的逻辑全部提取出来放在Helper里面,然后对Helper来做单元测试】
IOC的实现:
我这里用的是Unity, 所以首先需要安装Install-Package Unity.WebApi.5.1
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); //Install-Package Unity.WebApi.5.1 IUnityContainer container = new UnityContainer() .RegisterType<IArticleService, ArticleService>() .RegisterType<IBlogService, BlogService>(); //GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container); GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new UnityHttpControllerActivator(container)); } } public class UnityHttpControllerActivator : IHttpControllerActivator { public IUnityContainer UnityContainer { get; private set; } public UnityHttpControllerActivator(IUnityContainer unityContainer) { this.UnityContainer = unityContainer; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController)this.UnityContainer.Resolve(controllerType); } }
一般我们的controller都只用DependencyResolver和IHttpControllerActivator 来实现控制反转。
单元测试我一般用Moq 和nunit ,所以需要 Install-Package Moq 和 Install-Package NUnit
单元测试的code 如下:
public void TestPostArticle() { var article = new Article { Title = "Web API Unit Testing", URL = "https://chsakell.com/web-api-unit-testing", Author = "Chris Sakellarios", DateCreated = DateTime.Now, Contents = "Unit testing Web API.." }; var _articlesController = new ArticlesController(_articleService) { Configuration = new HttpConfiguration(), Request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri("http://localhost/api/articles") } }; var result = _articlesController.PostArticle(article) as CreatedAtRouteNegotiatedContentResult<Article>; Assert.That(result.RouteName, Is.EqualTo("DefaultApi")); Assert.That(result.Content.ID, Is.EqualTo(result.RouteValues["id"])); Assert.That(result.Content.ID, Is.EqualTo(_randomArticles.Max(a => a.ID))); }
在controller的实例的时候直接传递IArticleService 实例。
public static IArticleService GetIArticleService() { var _article = new Mock<IArticleService>(); _article.Setup(x => x.GetArticles(It.IsAny<string>())).Returns(new Func<string, List<Article>>(name => { if (string.IsNullOrEmpty(name)) { return _randomArticles; } else { return _randomArticles.FindAll(x => x.Title.Contains(name)); } })); _article.Setup(x => x.GetArticle(It.IsAny<int>())).Returns(new Func<int, Article>(id => { return _randomArticles.Find(x => x.ID == id); })); _article.Setup(x => x.GetArticle(It.IsAny<string>())).Returns(new Func<string, Article>(name => { return _randomArticles.Find(x => x.Title == name); })); _article.Setup(r => r.CreateArticle(It.IsAny<Article>())) .Callback(new Action<Article>(newArticle => { newArticle.DateCreated = DateTime.Now; newArticle.ID = _randomArticles.Last().ID + 1; _randomArticles.Add(newArticle); })); _article.Setup(r => r.UpdateArticle(It.IsAny<Article>())) .Callback(new Action<Article>(x => { var oldArticle = _randomArticles.Find(a => a.ID == x.ID); oldArticle.DateEdited = DateTime.Now; oldArticle.URL = x.URL; oldArticle.Title = x.Title; oldArticle.Contents = x.Contents; oldArticle.BlogID = x.BlogID; })); return _article.Object; }
由于WebApi一般都是用来提供接口的,使用者往往是以发送http请求来获取数据,我们也可以用这种方式来做单元测试:
public void TestPostArticle2() { var article = new Article { Title = "Web API Unit Testing2", URL = "https://chsakell.com/web-api-unit-testing", Author = "Chris Sakellarios", DateCreated = DateTime.Now, Contents = "Unit testing Web API.." }; var address = "http://localhost:9000/"; using (WebApp.Start<Startup>(address)) { HttpClient _client = new HttpClient(); var response = _client.PostAsJsonAsync<Article>(address + "api/articles/", article).Result; var result = response.Content.ReadAsAsync<Article>().Result; Assert.That(result.Title, Is.EqualTo(article.Title)); Assert.That(_randomArticles.Last().Title, Is.EqualTo(article.Title)); } }
那么我们如何托起这个web 程序,有如何 使用IOC,
这里IOC 用Autofac,其所用包如下:
Install-Package Owin
Install-Package Microsoft.AspNet.WebApi.Owin
Install-Package Microsoft.Owin.Host.HttpListener
Install-Package Microsoft.Owin.Hosting
Install-Package Autofac
Install-Package Autofac.WebApi2
public class Startup { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); // config.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver()); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); var builder = new ContainerBuilder(); builder.RegisterApiControllers(typeof(ArticlesController).Assembly); var _articleService = Helper.GetIArticleService(); builder.RegisterInstance(_articleService).As<IArticleService>(); IContainer container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); appBuilder.UseWebApi(config); } }
参考: