.NET

ASP.NET Web API Unit Testing

Unit testing can be beneficial to many aspects in software develepment, from the lowest level that is the source code to the highest level and the end user’s experience. Writing automated tests helps finding defects earlier in the development lifecycle process which leads to fewer late nights or weekend work (happier developers). Since defects are resolved before production, less defects reach end users (happier clients). It also increases reliability of source code, since if the base code doesn’t change all tests should always return the same results. Last but not least, anyone that decides to write unit tests is also forced to write testable code which leads to better software development practices.
 
 
 
web-api-unit-testing

Web API Unit Testing

ASP.NET Web API stack has many aspects that firstly must be well understood before writing unit tests against it and that’s what makes it difficult. This post is a full stack Web API Unit testing tutorial which means will show you how to unit test all the layers and components exist in your Web API application. Let’s see what we are gonna see on this post:

I will break the post in two main sections. The first one will be the one where we ‘re gonna structure the application and the second one will be the actual Unit testing. For the first one I will follow the Generic repository pattern which I have already describe in this post. If you feel familiar with those concepts and you just want to read about how the unit testing is done, you can skip this step. Mind though that part of this section will be the Controller registration of a referenced library which has an important role in our Unit testing.

Section One: Structuring the Web API Application

Create a new blank solution named UnitTestingWebAPI and add the following projects:

  1. UnitTestingWebAPI.Domain: Class library (Contains Entity Models)
  2. UnitTestingWebAPI.Data: Class library (Contains Repositories)
  3. UnitTestingWebAPI.Services: Class library (Contains Services)
  4. UnitTestingWebAPI.API.Core: Class library (Contains Web API components such as Controllers, Filters, Message Handlers)
  5. UnitTestingWebAPI.API: Empty ASP.NET Web Application (Web application to host Web API)
  6. UnitTestingWebAPI.Tests: Class library (Contains the Unit Tests)

Switch to UnitTestingWebAPI.Domain and add the following classes:

Article.cs

public class Article
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Contents { get; set; }
        public string Author { get; set; }
        public string URL { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateEdited { get; set; }
        public int BlogID { get; set; }
        public virtual Blog Blog { get; set; }
        public Article()
        {
        }
    }

Blog.cs

public class Blog
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string URL { get; set; }
        public string Owner { get; set; }
        public DateTime DateCreated { get; set; }
        public virtual ICollection<Article> Articles { get; set; }
        public Blog()
        {
            Articles = new HashSet<Article>();
        }
    }

Repository Layer

Switch to UnitTestingWebAPI.Data, install Entity Framework from Nuget packages, add a reference to UnitTestingWebAPI.Data and add the following classes (create the respective folder if required):

Configurations/ArticleConfiguration.cs

public class ArticleConfiguration : EntityTypeConfiguration<Article>
    {
        public ArticleConfiguration()
        {
            ToTable("Article");
            Property(a => a.Title).IsRequired().HasMaxLength(100);
            Property(a => a.Contents).IsRequired();
            Property(a => a.Author).IsRequired().HasMaxLength(50);
            Property(a => a.URL).IsRequired().HasMaxLength(200);
            Property(a => a.DateCreated).HasColumnType("datetime2");
            Property(a => a.DateEdited).HasColumnType("datetime2");
        }
    }

Configurations/BlogConfiguration.cs

public class BlogConfiguration : EntityTypeConfiguration<Blog>
    {
        public BlogConfiguration()
        {
            ToTable("Blog");
            Property(b => b.Name).IsRequired().HasMaxLength(100);
            Property(b => b.URL).IsRequired().HasMaxLength(200);
            Property(b => b.Owner).IsRequired().HasMaxLength(50);
            Property(b => b.DateCreated).HasColumnType("datetime2");
        }
    }
public class BloggerEntities : DbContext
    {
        public BloggerEntities()
            : base("BloggerEntities")
        {
            Configuration.ProxyCreationEnabled = false;
        }
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Article> Articles { get; set; }
        public virtual void Commit()
        {
            base.SaveChanges();
        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new ArticleConfiguration());
            modelBuilder.Configurations.Add(new BlogConfiguration());
        }
    }

BloggerInitializer.cs

public class BloggerInitializer : DropCreateDatabaseIfModelChanges<BloggerEntities>
    {
        protected override void Seed(BloggerEntities context)
        {
            GetBlogs().ForEach(b => context.Blogs.Add(b));
            context.Commit();
        }
        public static List<Blog> GetBlogs()
        {
            List<Blog> _blogs = new List<Blog>();
            // Add two Blogs
            Blog _chsakellsBlog = new Blog()
            {
                Name = "chsakell's Blog",
                URL = "http://chsakell.com/",
                Owner = "Chris Sakellarios",
                Articles = GetChsakellsArticles()
            };
            Blog _dotNetCodeGeeks = new Blog()
            {
                Name = "DotNETCodeGeeks",
                URL = "dotnetcodegeeks",
                Owner = ".NET Code Geeks",
                Articles = GetDotNETGeeksArticles()
            };
            _blogs.Add(_chsakellsBlog);
            _blogs.Add(_dotNetCodeGeeks);
            return _blogs;
        }
        public static List<Article> GetChsakellsArticles()
        {
            List<Article> _articles = new List<Article>();
            Article _oData = new Article()
            {
                Author = "Chris S.",
                Title = "ASP.NET Web API feat. OData",
                URL = "http://chsakell.com/2015/04/04/asp-net-web-api-feat-odata/",
                Contents = @"OData is an open standard protocol allowing the creation and consumption of queryable 
                            and interoperable RESTful APIs. It was initiated by Microsoft and it’s mostly known to
                            .NET Developers from WCF Data Services. There are many other server platforms supporting
                            OData services such as Node.js, PHP, Java and SQL Server Reporting Services. More over, 
                            Web API also supports OData and this post will show you how to integrate those two.."
            };
            Article _wcfCustomSecurity= new Article()
            {
                Author = "Chris S.",
                Title = "Secure WCF Services with custom encrypted tokens",
                URL = "http://chsakell.com/2014/12/13/secure-wcf-services-with-custom-encrypted-tokens/",
                Contents = @"Windows Communication Foundation framework comes with a lot of options out of the box, 
                            concerning the security logic you will apply to your services. Different bindings can be
                            used for certain kind and levels of security. Even the BasicHttpBinding binding supports
                            some types of security. There are some times though where you cannot or don’t want to use
                            WCF security available options and hence, you need to develop your own authentication logic
                            accoarding to your business needs."
            };
            _articles.Add(_oData);
            _articles.Add(_wcfCustomSecurity);
            return _articles;
        }
        public static List<Article> GetDotNETGeeksArticles()
        {
            List<Article> _articles = new List<Article>();
            Article _angularFeatWebAPI = new Article()
            {
                Author = "Gordon Beeming",
                Title = "AngularJS feat. Web API",
                URL = "http://www.dotnetcodegeeks.com/2015/05/angularjs-feat-web-api.html",
                Contents = @"Developing Web applications using AngularJS and Web API can be quite amuzing. You can pick 
                            this architecture in case you have in mind a web application with limitted page refreshes or
                            post backs to the server while each application’s View is based on partial data retrieved from it."
            };
            _articles.Add(_angularFeatWebAPI);
            return _articles;
        }
        public static List<Article> GetAllArticles()
        {
            List<Article> _articles = new List<Article>();
            _articles.AddRange(GetChsakellsArticles());
            _articles.AddRange(GetDotNETGeeksArticles());
            return _articles;
        }
    }

Infrastructure/Disposable.cs

public class Disposable : IDisposable
    {
        private bool isDisposed;
        ~Disposable()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if (!isDisposed && disposing)
            {
                DisposeCore();
            }
            isDisposed = true;
        }
        // Ovveride this to dispose custom objects
        protected virtual void DisposeCore()
        {
        }
    }

Infrastructure/IDbFactory.cs

public interface IDbFactory : IDisposable
    {
        BloggerEntities Init();
    }

Infrastructure/DbFactory.cs

public class DbFactory : Disposable, IDbFactory
    {
        BloggerEntities dbContext;
        public BloggerEntities Init()
        {
            return dbContext ?? (dbContext = new BloggerEntities());
        }
        protected override void DisposeCore()
        {
            if (dbContext != null)
                dbContext.Dispose();
        }
    }

Infrastrure/IRepository.cs

public interface IRepository<T> where T : class
    {
        // Marks an entity as new
        void Add(T entity);
        // Marks an entity as modified
        void Update(T entity);
        // Marks an entity to be removed
        void Delete(T entity);
        void Delete(Expression<Func<T, bool>> where);
        // Get an entity by int id
        T GetById(int id);
        // Get an entity using delegate
        T Get(Expression<Func<T, bool>> where);
        // Gets all entities of type T
        IEnumerable<T> GetAll();
        // Gets entities using delegate
        IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
    }

Infrastructure/RepositoryBase.cs

public abstract class RepositoryBase<T> where T : class
    {
        #region Properties
        private BloggerEntities dataContext;
        private readonly IDbSet<T> dbSet;
        protected IDbFactory DbFactory
        {
            get;
            private set;
        }
        protected BloggerEntities DbContext
        {
            get { return dataContext ?? (dataContext = DbFactory.Init()); }
        }
        #endregion
        protected RepositoryBase(IDbFactory dbFactory)
        {
            DbFactory = dbFactory;
            dbSet = DbContext.Set<T>();
        }
        #region Implementation
        public virtual void Add(T entity)
        {
            dbSet.Add(entity);
        }
        public virtual void Update(T entity)
        {
            dbSet.Attach(entity);
            dataContext.Entry(entity).State = EntityState.Modified;
        }
        public virtual void Delete(T entity)
        {
            dbSet.Remove(entity);
        }
        public virtual void Delete(Expression<Func<T, bool>> where)
        {
            IEnumerable<T> objects = dbSet.Where<T>(where).AsEnumerable();
            foreach (T obj in objects)
                dbSet.Remove(obj);
        }
        public virtual T GetById(int id)
        {
            return dbSet.Find(id);
        }
        public virtual IEnumerable<T> GetAll()
        {
            return dbSet.ToList();
        }
        public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
        {
            return dbSet.Where(where).ToList();
        }
        public T Get(Expression<Func<T, bool>> where)
        {
            return dbSet.Where(where).FirstOrDefault<T>();
        }
        #endregion
    
    }

Infrastrure/IUnitOfWork.cs

public interface IUnitOfWork
    {
        void Commit();
    }

Infrastrure/UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
    {
        private readonly IDbFactory dbFactory;
        private BloggerEntities dbContext;
        public UnitOfWork(IDbFactory dbFactory)
        {
            this.dbFactory = dbFactory;
        }
        public BloggerEntities DbContext
        {
            get { return dbContext ?? (dbContext = dbFactory.Init()); }
        }
        public void Commit()
        {
            DbContext.Commit();
        }
    }

Repositories/ArticleRepository.cs

public class ArticleRepository : RepositoryBase<Article>, IArticleRepository
    {
        public ArticleRepository(IDbFactory dbFactory)
            : base(dbFactory) { }
        public Article GetArticleByTitle(string articleTitle)
        {
            var _article = this.DbContext.Articles.Where(b => b.Title == articleTitle).FirstOrDefault();
            return _article;
        }
    }
    public interface IArticleRepository : IRepository<Article>
    {
        Article GetArticleByTitle(string articleTitle);
    }

Infrastructure/BlogRepository.cs

public class BlogRepository : RepositoryBase<Blog>, IBlogRepository
    {
        public BlogRepository(IDbFactory dbFactory)
            : base(dbFactory) { }
        public Blog GetBlogByName(string blogName)
        {
            var _blog = this.DbContext.Blogs.Where(b => b.Name == blogName).FirstOrDefault();
            return _blog;
        }
    }
    public interface IBlogRepository : IRepository<Blog>
    {
        Blog GetBlogByName(string blogName);
    }

Service layer

Switch to UnitTestingWebAPI.Service project, add references to UnitTestingWebAPI.Domain,UnitTestingWebAPI.Data and add the following two files:

ArticleService.cs

// operations you want to expose
    public interface IArticleService
    {
        IEnumerable<Article> GetArticles(string name = null);
        Article GetArticle(int id);
        Article GetArticle(string name);
        void CreateArticle(Article article);
        void UpdateArticle(Article article);
        void DeleteArticle(Article article);
        void SaveArticle();
    }
    public class ArticleService : IArticleService
    {
        private readonly IArticleRepository articlesRepository;
        private readonly IUnitOfWork unitOfWork;
        public ArticleService(IArticleRepository articlesRepository, IUnitOfWork unitOfWork)
        {
            this.articlesRepository = articlesRepository;
            this.unitOfWork = unitOfWork;
        }
        #region IArticleService Members
        public IEnumerable<Article> GetArticles(string title = null)
        {
            if (string.IsNullOrEmpty(title))
                return articlesRepository.GetAll();
            else
                return articlesRepository.GetAll().Where(c => c.Title.ToLower().Contains(title.ToLower()));
        }
        public Article GetArticle(int id)
        {
            var article = articlesRepository.GetById(id);
            return article;
        }
        public Article GetArticle(string title)
        {
            var article = articlesRepository.GetArticleByTitle(title);
            return article;
        }
        public void CreateArticle(Article article)
        {
            articlesRepository.Add(article);
        }
        public void UpdateArticle(Article article)
        {
            articlesRepository.Update(article);
        }
        public void DeleteArticle(Article article)
        {
            articlesRepository.Delete(article);
        }
        public void SaveArticle()
        {
            unitOfWork.Commit();
        }
        #endregion
    }

BlogService.cs

// operations you want to expose
    public interface IBlogService
    {
        IEnumerable<Blog> GetBlogs(string name = null);
        Blog GetBlog(int id);
        Blog GetBlog(string name);
        void CreateBlog(Blog blog);
        void UpdateBlog(Blog blog);
        void SaveBlog();
        void DeleteBlog(Blog blog);
    }
    public class BlogService : IBlogService
    {
        private readonly IBlogRepository blogsRepository;
        private readonly IUnitOfWork unitOfWork;
        public BlogService(IBlogRepository blogsRepository, IUnitOfWork unitOfWork)
        {
            this.blogsRepository = blogsRepository;
            this.unitOfWork = unitOfWork;
        }
        #region IBlogService Members
        public IEnumerable<Blog> GetBlogs(string name = null)
        {
            if (string.IsNullOrEmpty(name))
                return blogsRepository.GetAll();
            else
                return blogsRepository.GetAll().Where(c => c.Name == name);
        }
        public Blog GetBlog(int id)
        {
            var blog = blogsRepository.GetById(id);
            return blog;
        }
        public Blog GetBlog(string name)
        {
            var blog = blogsRepository.GetBlogByName(name);
            return blog;
        }
        public void CreateBlog(Blog blog)
        {
            blogsRepository.Add(blog);
        }
        public void UpdateBlog(Blog blog)
        {
            blogsRepository.Update(blog);
        }
        public void DeleteBlog(Blog blog)
        {
            blogsRepository.Delete(blog);
        }
        public void SaveBlog()
        {
            unitOfWork.Commit();
        }
        #endregion
    }

Web API Core Components

Switch to UnitTestingWebAPI.API.Core and add references to UnitTestingWebAPI.API.Domain and UnitTestingWebAPI.API.Service projects. Install the following packages from Nuget Packages:

  1. Entity Framework
  2. Microsoft.AspNet.WebApi.Core
  3. Microsoft.AspNet.WebApi.Client

Add the following Web API Controllers to a Controllers folder:

Controllers/ArticlesController.cs

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();
            }
            _articleService.UpdateArticle(article);
            try
            {
                _articleService.SaveArticle();
            }
            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;
        }
    }

Controllers/BlogsController.cs

public class BlogsController : ApiController
    {
        private IBlogService _blogService;
        public BlogsController(IBlogService blogService)
        {
            _blogService = blogService;
        }
        // GET: api/Blogs
        public IEnumerable<Blog> GetBlogs()
        {
            return _blogService.GetBlogs();
        }
        // GET: api/Blogs/5
        [ResponseType(typeof(Blog))]
        public IHttpActionResult GetBlog(int id)
        {
            Blog blog = _blogService.GetBlog(id);
            if (blog == null)
            {
                return NotFound();
            }
            return Ok(blog);
        }
        // PUT: api/Blogs/5
        [ResponseType(typeof(void))]
        public IHttpActionResult PutBlog(int id, Blog blog)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            if (id != blog.ID)
            {
                return BadRequest();
            }
            _blogService.UpdateBlog(blog);
            try
            {
                _blogService.SaveBlog();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!BlogExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return StatusCode(HttpStatusCode.NoContent);
        }
        // POST: api/Blogs
        [ResponseType(typeof(Blog))]
        public IHttpActionResult PostBlog(Blog blog)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            _blogService.CreateBlog(blog);
            return CreatedAtRoute("DefaultApi", new { id = blog.ID }, blog);
        }
        // DELETE: api/Blogs/5
        [ResponseType(typeof(Blog))]
        public IHttpActionResult DeleteBlog(int id)
        {
            Blog blog = _blogService.GetBlog(id);
            if (blog == null)
            {
                return NotFound();
            }
            _blogService.DeleteBlog(blog);
            return Ok(blog);
        }
        private bool BlogExists(int id)
        {
            return _blogService.GetBlog(id) != null;
        }
    }

Add the following filter which when applied, it reverses the order of a List of articles:

Filters/ArticlesReversedFilter.cs

public class ArticlesReversedFilter : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var objectContent = actionExecutedContext.Response.Content as ObjectContent;
            if (objectContent != null)
            {
                List<Article> _articles = objectContent.Value as List<Article>;
                if (_articles != null && _articles.Count > 0)
                {
                    _articles.Reverse();
                }
            }
        }
    }

Add the following MediaTypeFormatter which can return a comma serated representation of articles:

MediaTypeFormatters/ArticleFormatter.cs

public class ArticleFormatter : BufferedMediaTypeFormatter
    {
        public ArticleFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/article"));
        }
        public override bool CanReadType(Type type)
        {
            return false;
        }
        public override bool CanWriteType(Type type)
        {
            //for single article object
            if (type == typeof(Article))
                return true;
            else
            {
                // for multiple article objects
                Type _type = typeof(IEnumerable<Article>);
                return _type.IsAssignableFrom(type);
            }
        }
        public override void WriteToStream(Type type,
                                           object value,
                                           Stream writeStream,
                                           HttpContent content)
        {
            using (StreamWriter writer = new StreamWriter(writeStream))
            {
                var articles = value as IEnumerable<Article>;
                if (articles != null)
                {
                    foreach (var article in articles)
                    {
                        writer.Write(String.Format("[{0},\"{1}\",\"{2}\",\"{3}\",\"{4}\"]",
                                                    article.ID,
                                                    article.Title,
                                                    article.Author,
                                                    article.URL,
                                                    article.Contents));
                    }
                }
                else
                {
                    var _article = value as Article;
                    if (_article == null)
                    {
                        throw new InvalidOperationException("Cannot serialize type");
                    }
                    writer.Write(String.Format("[{0},\"{1}\",\"{2}\",\"{3}\",\"{4}\"]",
                                                    _article.ID,
                                                    _article.Title,
                                                    _article.Author,
                                                    _article.URL,
                                                    _article.Contents));
                }
            }
        }
    }

Add the following two Message Handlers. The first one is responsible to add a custom header in the response and the second one is able to terminate the request if applied:

MessageHandlers/HeaderAppenderHandler.cs

public class HeaderAppenderHandler : DelegatingHandler
    {
        async protected override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
            response.Headers.Add("X-WebAPI-Header", "Web API Unit testing in chsakell's blog.");
            return response;
        }
    }

HeaderAppenderHandler/EndRequestHandler.cs

public class EndRequestHandler : DelegatingHandler
    {
        async protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.RequestUri.AbsoluteUri.Contains("test"))
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StringContent("Unit testing message handlers!")
                };
                var tsc = new TaskCompletionSource<HttpResponseMessage>();
                tsc.SetResult(response);
                return await tsc.Task;
            }
            else
            {
                return await base.SendAsync(request, cancellationToken);
            }
        }
    }


Add the following DefaultAssembliesResolver which will be used for Controller registration from the Web Application project:

CustomAssembliesResolver.cs

public class CustomAssembliesResolver : DefaultAssembliesResolver
    {
        public override ICollection<Assembly> GetAssemblies()
        {
            var baseAssemblies = base.GetAssemblies().ToList();
            var assemblies = new List<Assembly>(baseAssemblies) { typeof(BlogsController).Assembly };
            baseAssemblies.AddRange(assemblies);
            return baseAssemblies.Distinct().ToList();
        }
    }

ASP.NET Web Application

Switch to UnitTestingWebAPI.API web application project and add references to UnitTestingWebAPI.Core,UnitTestingWebAPI.Data and UnitTestingWebAPI.Service. You will also need to install the following Nuget packages:

  1. Entity Framework
  2. Microsoft.AspNet.WebApi.WebHost
  3. Microsoft.AspNet.WebApi.Core
  4. Microsoft.AspNet.WebApi.Client
  5. Microsoft.AspNet.WebApi.Owin
  6. Microsoft.Owin.Host.SystemWeb
  7. Microsoft.Owin
  8. Autofac.WebApi2

Add a Global Configuration file if not exists and set the database initializer:

Global.asax

public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            // Init database
            System.Data.Entity.Database.SetInitializer(new BloggerInitializer());
        }
    }

Also make sure you add a relevant connection string in the Web.config file:

<connectionStrings>
    <add name="BloggerEntities" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=BloggerDB;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>

External Controller Registration

Create an Owin Startup.cs file at the root of the Web application and paste the following code. This code will ensure to use WebApi controllers from the UnitTestingWebAPI.API.Core project (CustomAssembliesResolver) and inject the appropriate repositories and services when required (autofac configuration):

Startup.cs

public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());
            config.Formatters.Add(new ArticleFormatter());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            // Autofac configuration
            var builder = new ContainerBuilder();
            builder.RegisterApiControllers(typeof(BlogsController).Assembly);
            builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
            builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();
            //Repositories
            builder.RegisterAssemblyTypes(typeof(BlogRepository).Assembly)
                .Where(t => t.Name.EndsWith("Repository"))
                .AsImplementedInterfaces().InstancePerRequest();
            // Services
            builder.RegisterAssemblyTypes(typeof(ArticleService).Assembly)
               .Where(t => t.Name.EndsWith("Service"))
               .AsImplementedInterfaces().InstancePerRequest();
            IContainer container = builder.Build();
            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
            appBuilder.UseWebApi(config);
        }
    }

At this point you should be able to fire the Web application and request articles or blogs using the following requests (port may be different in yours):

http://localhost:56032/api/articles
http://localhost:56032/api/blogs

Section Two: Unit Testing

We have completed structuring our application and it’s time to unit test all of our components. Switch to UnitTestingWebAPI.Tests class library and add references to UnitTestingWebAPI.Domain, UnitTestingWebAPI.Data,UnitTestingWebAPI.Service and UnitTestingWebAPI.API.Core. Also make sure you install the following Nuget Packages:

  1. Entity Framework
  2. Microsoft.AspNet.WebApi.Core
  3. Microsoft.AspNet.WebApi.Client
  4. Microsoft.AspNet.WebApi.Owin
  5. Microsoft.AspNet.WebApi.SelfHost
  6. Micoroft.Owin
  7. Owin
  8. Micoroft.Owin.Hosting
  9. Micoroft.Owin.Host.HttpListener
  10. Autofac.WebApi2
  11. NUnit
  12. NUnitTestAdapter

As you see we are going to use NUnit to write our unit tests.

Services Unit Testing

When writing Unit tests, first you need to setup or initiate some variables to be used for the unit tests. With NUnit this is done via a function with an attribute Setup applied on it. This very function will run before any NUnit test is executed. Unit testing the Service layer is the first thing you need to do since all the Controller’s constructors are injected with Services. Hence, you need to emulate repositories and service behavior before starting unit testing WebAPI Core components. In this example we ‘ll see how to emulate the ArticleSevice. This service’s constructor is injected with instances of IArticleRepository and IUnitOfWork so all we have to do is create two “special” instances and inject them.

ArticleService Constructor

public ArticleService(IArticleRepository articlesRepository, IUnitOfWork unitOfWork)
        {
            this.articlesRepository = articlesRepository;
            this.unitOfWork = unitOfWork;
        }

I said “special” cause those instance are not going to be real instances that actually can access the database.

Attention

Unit tests must run in memory and shouldn’t access databases. All core functionality must be emulated by using frameworks such as Mock in our case. This way automated tests will be much more faster. The basic purpose of unit tests is more testing component behavior rather than testing real results.

Let’s procceed with testing the ArticleService. Create a file named ServiceTests and for start add the following code:

ServicesTests.cs

[TestFixture]
    public class ServicesTests
    {
        #region Variables
        IArticleService _articleService;
        IArticleRepository _articleRepository;
        IUnitOfWork _unitOfWork;
        List<Article> _randomArticles;
        #endregion
        #region Setup
        [SetUp]
        public void Setup()
        {
            _randomArticles = SetupArticles();
            _articleRepository = SetupArticleRepository();
            _unitOfWork = new Mock<IUnitOfWork>().Object;
            _articleService = new ArticleService(_articleRepository, _unitOfWork);
        }
        public List<Article> SetupArticles()
        {
            int _counter = new int();
            List<Article> _articles = BloggerInitializer.GetAllArticles();
            foreach (Article _article in _articles)
                _article.ID = ++_counter;
            return _articles;
        }
        public IArticleRepository SetupArticleRepository()
        {
            // Init repository
            var repo = new Mock<IArticleRepository>();
            // Setup mocking behavior
            repo.Setup(r => r.GetAll()).Returns(_randomArticles);
            repo.Setup(r => r.GetById(It.IsAny<int>()))
                .Returns(new Func<int, Article>(
                    id => _randomArticles.Find(a => a.ID.Equals(id))));
            repo.Setup(r => r.Add(It.IsAny<Article>()))
                .Callback(new Action<Article>(newArticle =>
                {
                    dynamic maxArticleID = _randomArticles.Last().ID;
                    dynamic nextArticleID = maxArticleID + 1;
                    newArticle.ID = nextArticleID;
                    newArticle.DateCreated = DateTime.Now;
                    _randomArticles.Add(newArticle);
                }));
            repo.Setup(r => r.Update(It.IsAny<Article>()))
                .Callback(new Action<Article>(x =>
                    {
                        var oldArticle = _randomArticles.Find(a => a.ID == x.ID);
                        oldArticle.DateEdited = DateTime.Now;
                        oldArticle = x;
                    }));
            repo.Setup(r => r.Delete(It.IsAny<Article>()))
                .Callback(new Action<Article>(x =>
                {
                    var _articleToRemove = _randomArticles.Find(a => a.ID == x.ID);
                    if (_articleToRemove != null)
                        _randomArticles.Remove(_articleToRemove);
                }));
            // Return mock implementation
            return repo.Object;
        }
        #endregion
   }
}

In the SetupArticleRepository() function we emulate our _articleRepository behavior, in other words we setup what results are expected from this repository instance when a specific function is called. Then we inject this instance in our _articleService’s constructor and we are ready to go. Let’s say that we want to test that the _articleService.GetArticles() behaves as expected. Add the following NUnit test in the same file:

ServiceShouldReturnAllArticles Test

[Test]
        public void ServiceShouldReturnAllArticles()
        {
            var articles = _articleService.GetArticles();
            Assert.That(articles, Is.EqualTo(_randomArticles));
        }

Build the Tests project, run the test and make sure it passes. In the same way create the following tests:

Services Tests

[Test]
        public void ServiceShouldReturnRightArticle()
        {
            var wcfSecurityArticle = _articleService.GetArticle(2);
            Assert.That(wcfSecurityArticle,
                Is.EqualTo(_randomArticles.Find(a => a.Title.Contains("Secure WCF Services"))));
        }
        [Test]
        public void ServiceShouldAddNewArticle()
        {
            var _newArticle = new Article()
            {
                Author = "Chris Sakellarios",
                Contents = "If you are an ASP.NET MVC developer, you will certainly..",
                Title = "URL Rooting in ASP.NET (Web Forms)",
                URL = "http://chsakell.com/2013/12/15/url-rooting-in-asp-net-web-forms/"
            };
            int _maxArticleIDBeforeAdd = _randomArticles.Max(a => a.ID);
            _articleService.CreateArticle(_newArticle);
            Assert.That(_newArticle, Is.EqualTo(_randomArticles.Last()));
            Assert.That(_maxArticleIDBeforeAdd + 1, Is.EqualTo(_randomArticles.Last().ID));
        }
        [Test]
        public void ServiceShouldUpdateArticle()
        {
            var _firstArticle = _randomArticles.First();
            _firstArticle.Title = "OData feat. ASP.NET Web API"; // reversed :-)
            _firstArticle.URL = "http://t.co/fuIbNoc7Zh"; // short link
            _articleService.UpdateArticle(_firstArticle);
            Assert.That(_firstArticle.DateEdited, Is.Not.EqualTo(DateTime.MinValue));
            Assert.That(_firstArticle.URL, Is.EqualTo("http://t.co/fuIbNoc7Zh"));
            Assert.That(_firstArticle.ID, Is.EqualTo(1)); // hasn't changed
        }
        [Test]
        public void ServiceShouldDeleteArticle()
        {
            int maxID = _randomArticles.Max(a => a.ID); // Before removal
            var _lastArticle = _randomArticles.Last();
            // Remove last article
            _articleService.DeleteArticle(_lastArticle);
            Assert.That(maxID, Is.GreaterThan(_randomArticles.Max(a => a.ID))); // Max reduced by 1
        }

Web API Controllers Unit Testing

Now that we are familiar with emulating our services behavior we can procceed with unit testing Web API Controllers. First thing we need to do is Setup the variables to be used through our test, so create a ControllerTests.cs file and paste the following code:

[TestFixture]
    public class ControllerTests
    {
        #region Variables
        IArticleService _articleService;
        IArticleRepository _articleRepository;
        IUnitOfWork _unitOfWork;
        List<Article> _randomArticles;
        #endregion
        #region Setup
        [SetUp]
        public void Setup()
        {
            _randomArticles = SetupArticles();
            _articleRepository = SetupArticleRepository();
            _unitOfWork = new Mock<IUnitOfWork>().Object;
            _articleService = new ArticleService(_articleRepository, _unitOfWork);
        }
        public List<Article> SetupArticles()
        {
            int _counter = new int();
            List<Article> _articles = BloggerInitializer.GetAllArticles();
            foreach (Article _article in _articles)
                _article.ID = ++_counter;
            return _articles;
        }
        public IArticleRepository SetupArticleRepository()
        {
            // Init repository
            var repo = new Mock<IArticleRepository>();
            // Setup mocking behavior
            repo.Setup(r => r.GetAll()).Returns(_randomArticles);
            repo.Setup(r => r.GetById(It.IsAny<int>()))
                .Returns(new Func<int, Article>(
                    id => _randomArticles.Find(a => a.ID.Equals(id))));
            repo.Setup(r => r.Add(It.IsAny<Article>()))
                .Callback(new Action<Article>(newArticle =>
                {
                    dynamic maxArticleID = _randomArticles.Last().ID;
                    dynamic nextArticleID = maxArticleID + 1;
                    newArticle.ID = nextArticleID;
                    newArticle.DateCreated = DateTime.Now;
                    _randomArticles.Add(newArticle);
                }));
            repo.Setup(r => r.Update(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;
                }));
            repo.Setup(r => r.Delete(It.IsAny<Article>()))
                .Callback(new Action<Article>(x =>
                {
                    var _articleToRemove = _randomArticles.Find(a => a.ID == x.ID);
                    if (_articleToRemove != null)
                        _randomArticles.Remove(_articleToRemove);
                }));
            // Return mock implementation
            return repo.Object;
        }
        #endregion
   }
}

WebAPI Controller classes are classes just like all others so we can test them respectively. Let’s see if the _articlesController.GetArticles() does return all articles available:

ControlerShouldReturnAllArticles Unit test

[Test]
        public void ControlerShouldReturnAllArticles()
        {
            var _articlesController = new ArticlesController(_articleService);
            var result = _articlesController.GetArticles();
            CollectionAssert.AreEqual(result, _randomArticles);
        }

The most important line here is the highlighted one where the _articleService instance injection will ensure the service’s behavior.
In the same way we ensure that the last article is returned when invoking _articlesController.GetArticle(3) since we setup only 3 articles.

ControlerShouldReturnLastArticle Unit test

[Test]
        public void ControlerShouldReturnLastArticle()
        {
            var _articlesController = new ArticlesController(_articleService);
            var result = _articlesController.GetArticle(3) as OkNegotiatedContentResult<Article>;
            Assert.IsNotNull(result);
            Assert.AreEqual(result.Content.Title, _randomArticles.Last().Title);
        }

Let’s test that an invalid Update operation must fail and return a BadRequestResult. Recall the Update operation setup on the _articleRepository:

repo.Setup(r => r.Update(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;
                }));

So If we pass an non existing article this update should fail:

ControlerShouldPutReturnBadRequestResult Unit test

[Test]
        public void ControlerShouldPutReturnBadRequestResult()
        {
            var _articlesController = new ArticlesController(_articleService)
            {
                Configuration = new HttpConfiguration(),
                Request = new HttpRequestMessage
                {
                    Method = HttpMethod.Put,
                    RequestUri = new Uri("http://localhost/api/articles/-1")
                }
            };
            var badresult = _articlesController.PutArticle(-1, new Article() { Title = "Unknown Article" });
            Assert.That(badresult, Is.TypeOf<BadRequestResult>());
        }

Complete the Controller Unit testing by adding the following three tests which tests that updating first article succeeds, post new article succeeds and post new article fails respectivelly.

Controller Unit tests

[Test]
        public void ControlerShouldPutUpdateFirstArticle()
        {
            var _articlesController = new ArticlesController(_articleService)
            {
                Configuration = new HttpConfiguration(),
                Request = new HttpRequestMessage
                {
                    Method = HttpMethod.Put,
                    RequestUri = new Uri("http://localhost/api/articles/1")
                }
            };
            IHttpActionResult updateResult = _articlesController.PutArticle(1, new Article()
            {
                ID = 1,
                Title = "ASP.NET Web API feat. OData",
                URL = "http://t.co/fuIbNoc7Zh",
                Contents = @"OData is an open standard protocol.."
            }) as IHttpActionResult;
            Assert.That(updateResult, Is.TypeOf<StatusCodeResult>());
            StatusCodeResult statusCodeResult = updateResult as StatusCodeResult;
            Assert.That(statusCodeResult.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
            Assert.That(_randomArticles.First().URL, Is.EqualTo("http://t.co/fuIbNoc7Zh"));
        }
        [Test]
        public void ControlerShouldPostNewArticle()
        {
            var article = new Article
            {
                Title = "Web API Unit Testing",
                URL = "http://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")
                }
            };
            _articlesController.Configuration.MapHttpAttributeRoutes();
            _articlesController.Configuration.EnsureInitialized();
            _articlesController.RequestContext.RouteData = new HttpRouteData(
            new HttpRoute(), new HttpRouteValueDictionary { { "_articlesController", "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)));
        }
        [Test]
        public void ControlerShouldNotPostNewArticle()
        {
            var article = new Article
            {
                Title = "Web API Unit Testing",
                URL = "http://chsakell.com/web-api-unit-testing",
                Author = "Chris Sakellarios",
                DateCreated = DateTime.Now,
                Contents = null
            };
            var _articlesController = new ArticlesController(_articleService)
            {
                Configuration = new HttpConfiguration(),
                Request = new HttpRequestMessage
                {
                    Method = HttpMethod.Post,
                    RequestUri = new Uri("http://localhost/api/articles")
                }
            };
            _articlesController.Configuration.MapHttpAttributeRoutes();
            _articlesController.Configuration.EnsureInitialized();
            _articlesController.RequestContext.RouteData = new HttpRouteData(
            new HttpRoute(), new HttpRouteValueDictionary { { "Controller", "Articles" } });
            _articlesController.ModelState.AddModelError("Contents", "Contents is required field");
            var result = _articlesController.PostArticle(article) as InvalidModelStateResult;
            Assert.That(result.ModelState.Count, Is.EqualTo(1));
            Assert.That(result.ModelState.IsValid, Is.EqualTo(false));
        }

Take a good look the highlighted lines and see that we can unit test several aspects of our requests, such as CodeStatus returned or routing properties.

Message Handlers Unit Testing

You can test Message Handlers by creating an instance of HttpMessageInvoker, passing the Message Handler instance you want to test and invoke the SendAsync function. Create a MessageHandlerTests.cs file and paste the Setup code first:

MessageHandlerTests.cs

[TestFixture]
    public class MessageHandlerTests
    {
        #region Variables
        private EndRequestHandler _endRequestHandler;
        private HeaderAppenderHandler _headerAppenderHandler;
        #endregion
        #region Setup
        [SetUp]
        public void Setup()
        {
            // Direct MessageHandler test
            _endRequestHandler = new EndRequestHandler();
            _headerAppenderHandler = new HeaderAppenderHandler()
            {
                InnerHandler = _endRequestHandler
            };
        }
        #endregion
   }
}

We setup the HeaderAppenderHandler’s inner handler another Handler that will terminate the request. Recall that the EndRequestHandler will end the request only if the Uri contains a test literal. Let’s write the test now:

Direct ShouldAppendCustomHeader Unit Test

[Test]
        public async void ShouldAppendCustomHeader()
        {
            var invoker = new HttpMessageInvoker(_headerAppenderHandler);
            var result = await invoker.SendAsync(new HttpRequestMessage(HttpMethod.Get, 
                new Uri("http://localhost/api/test/")), CancellationToken.None);
            Assert.That(result.Headers.Contains("X-WebAPI-Header"), Is.True);
            Assert.That(result.Content.ReadAsStringAsync().Result, 
                Is.EqualTo("Unit testing message handlers!"));
        }

Now let’s pick up tha pace a little bit and make things quite more interesting. Let’s say you want to make an integration test that is you want to test the actual behavior of your Message Handler when a request is dispatched to a controller’s action. This would require to host the Web API and then run the unit test but is this possible here? Of course it, and this is the beauty when you create a highly loosely coupled application. All you have to do is Self host the web api and setup the appropriate configurations. In our case we are gonna host the web api and also setup Moq instances to be injected for Repositories and Services. Add the following Startup.cs file in the UnitTestingWebAPI.Tests project:

Hosting/Startup.cs

public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.MessageHandlers.Add(new HeaderAppenderHandler());
            config.MessageHandlers.Add(new EndRequestHandler());
            config.Filters.Add(new ArticlesReversedFilter());
            config.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            config.MapHttpAttributeRoutes();
            // Autofac configuration
            var builder = new ContainerBuilder();
            builder.RegisterApiControllers(typeof(ArticlesController).Assembly);
            // Unit of Work
            var _unitOfWork = new Mock<IUnitOfWork>();
            builder.RegisterInstance(_unitOfWork.Object).As<IUnitOfWork>();
            //Repositories
            var _articlesRepository = new Mock<IArticleRepository>();
            _articlesRepository.Setup(x => x.GetAll()).Returns(
                    BloggerInitializer.GetAllArticles()
                );
            builder.RegisterInstance(_articlesRepository.Object).As<IArticleRepository>();
            var _blogsRepository = new Mock<IBlogRepository>();
            _blogsRepository.Setup(x => x.GetAll()).Returns(
                BloggerInitializer.GetBlogs
                );
            builder.RegisterInstance(_blogsRepository.Object).As<IBlogRepository>();
            // Services
            builder.RegisterAssemblyTypes(typeof(ArticleService).Assembly)
               .Where(t => t.Name.EndsWith("Service"))
               .AsImplementedInterfaces().InstancePerRequest();
            builder.RegisterInstance(new ArticleService(_articlesRepository.Object, _unitOfWork.Object));
            builder.RegisterInstance(new BlogService(_blogsRepository.Object, _unitOfWork.Object));
            IContainer container = builder.Build();
            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
            appBuilder.UseWebApi(config);
        }
    }

Notice that it’s quite similar to the Startup class we wrote for the Web Application project, except that fake repositories and services are used. Now let’s return and write this integration test:

ShouldCallToControllerActionAppendCustomHeader Unit test

[Test]
        public void ShouldCallToControllerActionAppendCustomHeader()
        {
            //Arrange
            var address = "http://localhost:9000/";
            using (WebApp.Start<Startup>(address))
            {
                HttpClient _client = new HttpClient();
                var response = _client.GetAsync(address + "api/articles").Result;
                Assert.That(response.Headers.Contains("X-WebAPI-Header"), Is.True);
                var _returnedArticles = response.Content.ReadAsAsync<List<Article>>().Result;
                Assert.That(_returnedArticles.Count, Is.EqualTo( BloggerInitializer.GetAllArticles().Count));
            }
        }

Since the request doesn’t contain a test literal, it will reach the controller’s action and also bring the results. Notice also that the custom header has also been appended.

Action Filters Unit Testing

Recall that we had created an ArticlesReversedFilter that when applied it reverses the order of the articles that should be returned. We can either direct unit test this filter or run an integration one. We will see how to do both of them. To direct test an action filter you need to run it’s OnActionExecuted function by passing a instance of HttpActionExecutedContext as a parameter as follow:

ShouldSortArticlesByTitle Unit test (direct)

[Test]
        public void ShouldSortArticlesByTitle()
        {
            var filter = new ArticlesReversedFilter();
            var executedContext = new HttpActionExecutedContext(new HttpActionContext
            {
                Response = new HttpResponseMessage(),
            }, null);
            executedContext.Response.Content = new ObjectContent<List<Article>>(new List<Article>(_articles), new JsonMediaTypeFormatter());
            filter.OnActionExecuted(executedContext);
            var _returnedArticles = executedContext.Response.Content.ReadAsAsync<List<Article>>().Result;
            Assert.That(_returnedArticles.First(), Is.EqualTo(_articles.Last()));
        }

To run an integration test you need to self host the Web API and make the appropriate request. Mind that the filter must be registered in the Startup configuration class.

ShouldCallToControllerActionReverseArticles Unit test (integration)

[Test]
        public void ShouldCallToControllerActionReverseArticles()
        {
            //Arrange
            var address = "http://localhost:9000/";
            using (WebApp.Start<Startup>(address))
            {
                HttpClient _client = new HttpClient();
                var response = _client.GetAsync(address + "api/articles").Result;
                var _returnedArticles = response.Content.ReadAsAsync<List<Article>>().Result;
                Assert.That(_returnedArticles.First().Title, Is.EqualTo(BloggerInitializer.GetAllArticles().Last().Title));
            }
        }

Media Type formatters Unit Testing

You have created some custom Media Type formatters and you want to test their behavior. Recall the ArticleFormatter we created in the UnitTestingWebAPI.API.Core project and it’s able to return a comma separated string representation of articles. It can only write Article instances, not read ones or understand other type of classes. You need to set the Accept request header to application/article in order to apply the formatter. Let’s see the Setup configuration of our tests:

[TestFixture]
    public class MediaTypeFormatterTests
    {
        #region Variables
        Blog _blog;
        Article _article;
        ArticleFormatter _formatter;
        #endregion
        #region Setup
        [SetUp]
        public void Setup()
        {
            _blog = BloggerInitializer.GetBlogs().First();
            _article = BloggerInitializer.GetChsakellsArticles().First();
            _formatter = new ArticleFormatter();
        }
        #endregion
   }
}

You can test a MediaTypeFormatter by creating an instance of ObjectContent, passing the object to check if can be formatted by the respective formatter, and the formatter itself. If the formatter cannot read or write the passed object an exception will be thrown, otherwise not. For example let’s ensure that the ArticleFormatter cannot understand Blog instances:

FormatterShouldThrowExceptionWhenUnsupportedType Unit test

[Test]
        public void FormatterShouldThrowExceptionWhenUnsupportedType()
        {
            Assert.Throws<InvalidOperationException>(() => new ObjectContent<Blog>(_blog, _formatter));
        }

On the other hand it must work fine with parsing Article objects:

FormatterShouldNotThrowExceptionWhenArticle Unit test

[Test]
        public void FormatterShouldNotThrowExceptionWhenArticle()
        {
            Assert.DoesNotThrow(() => new ObjectContent<Article>(_article, _formatter));
        }

And here are some other tests you can run against your custom Media type formatters:

Media Type Formatters Unit tests

[Test]
        public void FormatterShouldHeaderBeSetCorrectly()
        {
            var content = new ObjectContent<Article>(_article, new ArticleFormatter());
            Assert.That(content.Headers.ContentType.MediaType, Is.EqualTo("application/article"));
        }
        [Test]
        public async void FormatterShouldBeAbleToDeserializeArticle()
        {
            var content = new ObjectContent<Article>(_article, _formatter);
            var deserializedItem = await content.ReadAsAsync<Article>(new[] { _formatter });
            Assert.That(_article, Is.SameAs(deserializedItem));
        }
        [Test]
        public void FormatterShouldNotBeAbleToWriteUnsupportedType()
        {
            var canWriteBlog = _formatter.CanWriteType(typeof(Blog));
            Assert.That(canWriteBlog, Is.False);
        }
        [Test]
        public void FormatterShouldBeAbleToWriteArticle()
        {
            var canWriteArticle = _formatter.CanWriteType(typeof(Article));
            Assert.That(canWriteArticle, Is.True);
        }

Routing Unit Testing

You want to test your routing configuration without hosting Web API. For this you ‘ll need a helper class that is able to return the Controller type or the controller’s action from an instance of a HttpControllerContext. Before this you have to create an HttpConfiguration with your routing configuration setup in it. Let’s see first the helper class:

Helpers/ControllerActionSelector.cs

public class ControllerActionSelector
    {
        #region Variables
        HttpConfiguration config;
        HttpRequestMessage request;
        IHttpRouteData routeData;
        IHttpControllerSelector controllerSelector;
        HttpControllerContext controllerContext;
        #endregion
        #region Constructor
        public ControllerActionSelector(HttpConfiguration conf, HttpRequestMessage req)
        {
            config = conf;
            request = req;
            routeData = config.Routes.GetRouteData(request);
            request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
            controllerSelector = new DefaultHttpControllerSelector(config);
            controllerContext = new HttpControllerContext(config, routeData, request);
        }
        #endregion
        #region Methods
        public string GetActionName()
        {
            if (controllerContext.ControllerDescriptor == null)
                GetControllerType();
            var actionSelector = new ApiControllerActionSelector();
            var descriptor = actionSelector.SelectAction(controllerContext);
            return descriptor.ActionName;    
        }
        public Type GetControllerType()
        {
            var descriptor = controllerSelector.SelectController(request);
            controllerContext.ControllerDescriptor = descriptor;
            return descriptor.ControllerType;
        }
        #endregion
    }

And now the RouteTests Setup configuration:

RouteTests.cs

[TestFixture]
    public class RouteTests
    {
        #region Variables
        HttpConfiguration _config;
        #endregion
        #region Setup
        [SetUp]
        public void Setup()
        {
            _config = new HttpConfiguration();
            _config.Routes.MapHttpRoute(name: "DefaultWebAPI", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional });
        }
        #endregion
        #region Helper methods
        public static string GetMethodName<T, U>(Expression<Func<T, U>> expression)
        {
            var method = expression.Body as MethodCallExpression;
            if (method != null)
                return method.Method.Name;
            throw new ArgumentException("Expression is wrong");
        }
        #endregion

Let’s see that a request to api/articles/5 invokes the ArticlesController.GetArticle(int id) function:

RouteShouldControllerGetArticleIsInvoked Unit test

[Test]
        public void RouteShouldControllerGetArticleIsInvoked()
        {
            var request = new HttpRequestMessage(HttpMethod.Get, "http://www.chsakell.com/api/articles/5");
            var _actionSelector = new ControllerActionSelector(_config, request);
            Assert.That(typeof(ArticlesController), Is.EqualTo(_actionSelector.GetControllerType()));
            Assert.That(GetMethodName((ArticlesController c) => c.GetArticle(5)),
                Is.EqualTo(_actionSelector.GetActionName()));
        }

We used some reflection to get controller’s action name. In the same way we can test that the post action is invoked:

RouteShouldPostArticleActionIsInvoked Unit test

[Test]
        public void RouteShouldPostArticleActionIsInvoked()
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "http://www.chsakell.com/api/articles/");
            var _actionSelector = new ControllerActionSelector(_config, request);
            Assert.That(GetMethodName((ArticlesController c) =>
                c.PostArticle(new Article())), Is.EqualTo(_actionSelector.GetActionName()));
        }

You will probably want to test that an invalid route is not working:

RouteShouldInvalidRouteThrowException Unit test

[Test]
        public void RouteShouldInvalidRouteThrowException()
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "http://www.chsakell.com/api/InvalidController/");
            var _actionSelector = new ControllerActionSelector(_config, request);
            Assert.Throws<HttpResponseException>(() => _actionSelector.GetActionName());
        }

Conclusion

We have seen many aspects of Unit Testing in Web API stack such as mocking the Service layer, unit testing Controllers, Message Handlers, Filters, Custom Media type Formatters and the routing configuration. Try to always writing unit tests for your application and you will never regret it. The most unit tests you write the more benefits you will get. For example a simple change in your repository may brake many aspects in your application. If the appropriate tests have been written, then in the first run you should see all broken parts of your application immediately. I hope you liked the post as much I did.

  • You can download the source code for this project here.
Reference: ASP.NET Web API Unit Testing from our NCG partner Christos Sakellarios at the chsakell’s Blog blog.

Christos Sakellarios

Senior Software Engineer, Blogger

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button