現在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); } }
參考: