MVC单元测试,使用Repository模式、Ninject、Moq

本篇使用Repository设计MVC项目,使用Ninject作为DI容器,借助Moq进行单元测试。

 

  模型和EF上下文

模型很简单:

public class Foo
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

EF上下文为:

using System.Data.Entity;

namespace MvcApplication1.Models
{
    public class FooBarContext : DbContext
    {
        public DbSet<Foo> Foos { get; set; }
    }
}

 

  Repository相关

为了避免在IXXXRepository中有关增删改查等的重复代码,有必要创建一个所有IXXXRepository的基接口:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace MvcApplication1.Repository
{
    public interface IBaseRepository<T> where T : class
    {
        IQueryable<T> GetAll();
        IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        void Edit(T entity);
        void Delete(T entity);
        void Save();
    }
}

IFooRepository,也可以有自己的接口方法:

using MvcApplication1.Models;

namespace MvcApplication1.Repository
{
    public interface IFooRepository : IBaseRepository<Foo>
    {
        Foo GetSingle(int fooId);
    }
}

BaseRepository是一个抽象类,提供了所有XXXRepository的泛型基类实现,并实现 IBaseRepository接口:

using System.Data.Entity;
using System.Linq;

namespace MvcApplication1.Repository
{
    public abstract class BaseRepository<C,T> : IBaseRepository<T> where T : class where C : DbContext,new()
    {
        private C _db = new C();

        public C Db
        {
            get { return _db; }
            set { _db = value; }
        }

        public System.Linq.IQueryable<T> GetAll()
        {
            IQueryable<T> query = _db.Set<T>();
            return query;
        }

        public System.Linq.IQueryable<T> FindBy(System.Linq.Expressions.Expression<System.Func<T, bool>> predicate)
        {
            IQueryable<T> query = _db.Set<T>().Where(predicate);
            return query;
        }

        public void Add(T entity)
        {
            _db.Set<T>().Add(entity);
        }

        public void Edit(T entity)
        {
            _db.Entry(entity).State = EntityState.Modified;
        }

        public void Delete(T entity)
        {
            _db.Set<T>().Remove(entity);
        }

        public void Save()
        {
            _db.SaveChanges();
        }
    }
}

FooRepository不仅派生于BaseRepository<FooBarContext, Foo>,还需要实现IFooRepository约定的接口方法:

using System.Linq;
using MvcApplication1.Models;

namespace MvcApplication1.Repository
{
    public class FooRepository : BaseRepository<FooBarContext, Foo>,IFooRepository
    {

        public Foo GetSingle(int fooId)
        {
            var query = GetAll().FirstOrDefault(x => x.Id == fooId);
            return query;
        }
    }
}

 

  Ninject控制器工厂

通过GuGet安装Ninjct,创建Ninject控制器工厂:

using System.Web.Mvc;
using MvcApplication1.Repository;
using Ninject;

namespace MvcApplication1.Extension
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;

        public NinjectControllerFactory()
        {
            ninjectKernel = new StandardKernel();
        }

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, System.Type controllerType)
        {
            return controllerType == null ? null : (IController) ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            ninjectKernel.Bind<IFooRepository>().To<FooRepository>();
            ninjectKernel.Bind<IBarRepository>().To<BarRepository>();
        }
    }
}

并在全局注册:

protected void Application_Start()
        {
            ......

            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
        }

 

  创建FooController,包含增删改查

using System;
using System.Web.Mvc;
using MvcApplication1.Models;
using MvcApplication1.Repository;

namespace MvcApplication1.Controllers
{
    public class FooController : Controller
    {
        private readonly IFooRepository _fooRepository;

        public FooController(IFooRepository fooRepository)
        {
            _fooRepository = fooRepository;
        }

        public ViewResult Index()
        {
            var model = _fooRepository.GetAll();
            return View(model);
        }

        public ActionResult Details(int id)
        {
            var model = _fooRepository.GetSingle(id);
            if (model == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }

        public ActionResult Edit(int id)
        {
            var model = _fooRepository.GetSingle(id);
            if (model == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }

        [ActionName("Edit"), HttpPost]
        public ActionResult Eidt_Post(Foo foo)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _fooRepository.Edit(foo);
                    _fooRepository.Save();
                    return RedirectToAction("Details", new { id = foo.Id });
                }
                catch (Exception ex)
                {
                   ModelState.AddModelError(string.Empty, "出错了:" + ex.Message);
                }
            }
            return View(foo);
        }

        public ActionResult Create()
        {
            return View();
        }

        [ActionName("Create"), HttpPost]
        public ActionResult Create_Post(Foo foo)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _fooRepository.Add(foo);
                    _fooRepository.Save();
                    return RedirectToAction("Details", new {id = foo.Id});
                }
                catch (Exception ex)
                {
                   ModelState.AddModelError(string.Empty, "出错了:"+ex.Message);
                }
            }
            return View(foo);
        }

        public ActionResult Delete(int id)
        {
            var model = _fooRepository.GetSingle(id);
            if (model == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }

        [ActionName("Delete"),HttpPost]
        public ActionResult Delete_Post(int id)
        {
            var model = _fooRepository.GetSingle(id);
            if (model == null)
            {
                return HttpNotFound();
            }
            _fooRepository.Delete(model);
            _fooRepository.Save();
            return RedirectToAction("Index");
        }
    }
}

 

  单元测试

通过NuGet安装Moq,借助Moq来模拟接口方法的返回值。

→初始化

private IFooRepository fooRepository;

        [TestInitialize]
        public void Initialize()
        {
            Mock<IFooRepository> mock = new Mock<IFooRepository>();
            mock.Setup(m => m.GetAll()).Returns(new Foo[]
            {
                new Foo(){Id = 1, Name = "Fake Foo 1"}, 
                new Foo(){Id = 2, Name = "Fake Foo 2"}, 
                new Foo(){Id = 3, Name = "Fake Foo 3"}, 
                new Foo(){Id = 4, Name = "Fake Foo 4"}
            }.AsQueryable());

            mock.Setup(m =>
            m.GetSingle(It.Is<int>(i =>i == 1 || i == 2 || i == 3 || i == 4))).Returns<int>(r => new Foo
            {
                Id = r,
                Name = string.Format("Fake Foo {0}", r)
            });

            fooRepository = mock.Object;
        }

 

→测试返回类型

[TestMethod]
        public void is_index_return_model_type_of_iqueryable_foo()
        {
            //Arragne
            FooController fooController = new FooController(fooRepository);

            //Act
            var indexModel = fooController.Index().Model;

            //Assert
            Assert.IsInstanceOfType(indexModel, typeof(IQueryable<Foo>));
        }

        [TestMethod]
        public void is_details_returns_type_of_ViewResult()
        {
            //Arrange
            FooController fooController = new FooController(fooRepository);

            //Act
            var detailsResult = fooController.Details(1);

            //Assert
            Assert.IsInstanceOfType(detailsResult, typeof(ViewResult));
        }

        [TestMethod]
        public void is_details_returns_type_of_HttpNotFoundResult()
        {
            //Arrange
            FooController fooController = new FooController(fooRepository);

            //Act
            var detailsResult = fooController.Details(5);

            //Assert
            Assert.IsInstanceOfType(detailsResult, typeof(HttpNotFoundResult));
        }

→测试返回集合类型Model的数量  

 

结果:

 

参考资料:               
How to Work With Generic Repositories on ASP.NET MVC and Unit Testing Them By Mocking

MVC单元测试,使用Repository模式、Ninject、Moq,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。