Asp.net WebApi + EF 單元測試架構 DbContext一站到底


其實關於webapi和Ef service的單元測試我以前已經寫過相關文章,大家可以參考:

Asp.net WebAPI 單元測試

單元測試 mock EF 中DbContext 和DbSet Include

先看一下項目結構圖:

這個demo非常簡單,UTWebApi.Data 是純粹的數據定義,UTWebApi.Service是我們的業務服務邏輯層,UTWebApi 是我們webapi的實現,UTWebApi.Tests就是測試項目。

數據層:

BloggerDbContext的構造函數一般都是一個,有些時候也會有多個,如:

如果你的DbContext包含數據庫中所有的表,那么只要第一個構造函數就可以了,但是如果你的表在幾個DbContext中,那么第二構造函數可能需要了, 比如你需要同時操作10張表,那么這10張表的操作應該在同一個事務里面吧,但是他們分布在2個DbContext里面,所以這2個DbContext應該用一個連接。

 internal class ArticleConfiguration : EntityTypeConfiguration<Article>實體的配置類不應該是public。

 

服務層:

我們首先需要一個基類的service如下

當然很多項目開發的時候喜歡用Repository模式, 我這里也簡單實現如下:

而我們具體的服務實現也就很簡單了   public ArticleService(BloggerDbContext ctx) : base(ctx) { }

 

webapi層:

Asp.net WebAPI 單元測試 里面webapi的IOC 使用Unity.WebApi 對應的測試用的是Autofac.WebApi2,這次我們換成StructureMap(今天在公司嘗試webapi里面用Unity,發現以前它的service也在用unity,但是以前用的是2.1,現在unity.webapi用的是5.1,所以要用unity還要改以前服務層的code,於是乎就用StructureMap)。需要開發2個幫助類:

    public class StructureMapScope : IDependencyScope
    {
        private readonly IContainer container;

        public StructureMapScope(IContainer container)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            this.container = container;
        }

        public object GetService(Type serviceType)
        {
            if (serviceType == null)
            {
                return null;
            }

            if (serviceType.IsAbstract || serviceType.IsInterface)
            {
                return this.container.TryGetInstance(serviceType);
            }

            return this.container.GetInstance(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return this.container.GetAllInstances(serviceType).Cast<object>();
        }

        public void Dispose()
        {
            this.container.Dispose();
        }
    }

    public class StructureMapDependencyResolver : StructureMapScope, IDependencyResolver
    {
        private readonly IContainer container;

        public StructureMapDependencyResolver(IContainer container)
            : base(container)
        {
            this.container = container;
        }

        public IDependencyScope BeginScope()
        {
            var childContainer = this.container.GetNestedContainer();
            return new StructureMapScope(childContainer);
        }
    }
View Code

具體使用如下:

api運行結果如圖:

 

測試項目:

首先測試需要mock DBContext,大家可以參考單元測試 mock EF 中DbContext 和DbSet Include 來做,其次我們需要一個DbHelper,用它來貨物DbContext 和初始化數據:

那么測試項目的code 也就非常簡單:

看這里的測試已經通過了, 這個DbContext是所有的測試共用的,就如同我們的數據庫一樣,尤其是DbContext的表比較多(500以上),那么mock這個DbContext需要花費比較長的時間。

整個項目的結構就說完了,一般我們的controller的ioc都是做服務層,而服務層又需要ioc數據層,就像我前面的Asp.net WebAPI 單元測試里面的GetIArticleService方法,其實現還是比較累的,太多的mock了,

同時在controller那里的ioc 的code也就比較多了,每增加一個controller就需要增加一個對應的service,那么ioc對應的配置也要增加 是不是很麻煩了? 所以索性直接ioc DbContext。因為在我們很多實際項目中service中的code 重用性不是很高,每當增加controller的時候,一般都要增加service去實現相應的邏輯,比如一個查詢邏輯現在有3個service已經實現了,但是在controller你可能不直接用這3個service, 而是重新建一個service,把3個EF 查詢合並成一個,這樣EF訪問3次DB 就變為一次,並且不需要的數據也不會返回了。所以這里的DbContext真的是一站到底, 從數據層經過服務層最后堅持到接口層,而單元測試的難點就在如何mock DbContext,這里我們采用萬劍歸中的方式來做(表達式給DbContext的DbSet賦值)。

 下載地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM