相关博文:《ASP.NET 5 使用 TestServer 进行单元测试

在上一篇博文中,主要说的是,使用 TestServer 对 ASP.NET 5 WebApi 进行单元测试,依赖注入在 WebApi Startup.cs 中完成,所以 UnitTest 中只需要使用 TestServer 启动 WebApi 站点就可以了,因为整个解决方案的对象都是用 ASP.NET 5 “自带”的依赖注入进行管理,所以,在对 ASP.NET 5 类库进行单元测试的时候,我都是手动进行 new 创建的对象,比如针对 Application 的单元测试,贴一段 AdImageServiceTest 中的代码:

namespace CNBlogs.Ad.Application.Tests
{
public class AdTextServiceTest : BaseTest
{
private IAdTextService _adTextService; public AdTextServiceTest(ITestOutputHelper output)
: base(output)
{
Bootstrapper.Startup.ConfigureMapper(); IUnitOfWork unitOfWork = new UnitOfWork(dbContext);
IMemcached memcached = new EnyimMemcached(null);
_adTextService = new AdTextService(new AdTextRepository(dbContext),
new AdTextCreativeRepository(dbContext),
new UserService(memcached),
unitOfWork,
memcached);
} [Fact]
public async Task GetByAdTextsTest()
{
var adTexts = await _adTextService.GetAdTexts();
Assert.NotNull(adTexts);
adTexts.ForEach(x => Console.WriteLine($"{x.Title}({x.Link})"));
}
}
}

AdImageServiceTest 构造函数中的代码,是不是看起来很别扭呢?我当时这样写,也是没有办法的,因为依赖注入的配置是写在 Startup 中,比如下面代码:

namespace CNBlogs.Ad.WebApi
{
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
} public IConfigurationRoot Configuration { get; set; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(); services.AddSingleton<IUnitOfWork, UnitOfWork>();
services.AddScoped<IDbContext, EFDbContext>();
services.AddSingleton<IAdTextRepository, AdTextRepository>();
services.AddSingleton<IAdTextService, AdTextService>();
}
}
}

这样设计导致的结果是,针对类库项目的单元测试,就没办法使用依赖注入获取对象了,我后来想使用针对 WebApi 单元测试的方式,来对类库进行单元测试,比如用 TestServer 来启动,但类库中没有办法获取所注入的对象,构成函数注入会报错,[FromServices] 属性注入是 MVC Controller 中的东西,并不支持,所以针对类库的单元测试,和 WebApi 的单元测试并不是一样。

IServiceCollection 的程序包是 Microsoft.Extensions.DependencyInjection.Abstractions,我原来以为它和 ASP.NET 5 Web 应用程序相关,其实它们也没啥关系,你可以脱离 ASP.NET 5 Web 应用程序,独立使用它,比如在类库的单元测试中,但如果这样设计使用,我们首先需要做一个工作,把 Startup.cs 中的 ConfigureServices 配置,独立出来,比如放在 CNBlogs.Ad.Bootstrapper 中,这样 Web 应用程序和单元测试项目,都可以使用它,减少代码的重复,比如我们可以进行下面设计:

namespace CNBlogs.Ad.Bootstrapper
{
public static class Startup
{
public static void Configure(this IServiceCollection services, string connectionString)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<EFDbContext>(options => options.UseSqlServer(connectionString)); services.AddEnyimMemcached(); ConfigureMapper(); services.AddSingleton<IUnitOfWork, UnitOfWork>();
services.AddScoped<IDbContext, EFDbContext>();
services.AddSingleton<IAdTextRepository, AdTextRepository>();
services.AddSingleton<IAdTextService, AdTextService>();
} public static void ConfigureMapper()
{
Mapper.CreateMap<CNBlogs.Ad.Domain.Entities.AdText, AdTextDTO>();
}
}
}

ASP.NET 5 WebApi 项目中的 Startup.cs 配置会非常简单,只需要下面代码:

public void ConfigureServices(IServiceCollection services)
{
services.Configure(Configuration["data:ConnectionString"]);//add using CNBlogs.Ad.Bootstrapper;
}

好了,做好上面工作后,单元测试中使用依赖注入就非常简单了,为了减少重复代码,我们可以先抽离出一个 BaseTest:

namespace CNBlogs.Ad.BaseTests
{
public class BaseTest
{
protected readonly ITestOutputHelper output;
protected IServiceProvider provider; public BaseTest(ITestOutputHelper output)
{
var connectionString = "";
var services = new ServiceCollection();
this.output = output;
services.Configure(connectionString);////add using CNBlogs.Ad.Bootstrapper;
provider = services.BuildServiceProvider();
}
}
}

可以看到,我们并没有使用 TestServer,而是手动创建一个 ServiceCollection,它有点类似于我们之前写依赖注入的 Unity Container,不过它们有很大不同,ServiceCollection 的功能更加强大,从 Bootstrapper.Startup 中可以看到,它可以注入 EF、MVC、Memcache 等等服务对象,BuildServiceProvider 的作用就是获取注入的服务对象,我们下面会用到:

namespace CNBlogs.Ad.Repository.Tests
{
public class AdTextServiceTest : BaseTest
{
private IAdTextService _adTextService; public AdTextServiceTest(ITestOutputHelper output)
: base(output)
{
_adTextService = provider.GetService<IAdTextService>();
} [Fact]
public async Task GetByAdTextsTest()
{
var adTexts = await _adTextService.GetAdTexts();
Assert.NotNull(adTexts);
adTexts.ForEach(x => Console.WriteLine($"{x.Title}({x.Link})"));
}
}
}

这段代码和一开始的那段代码,形成了鲜明对比,这就是代码设计的魅力所在!!!

ASP.NET 5 单元测试中使用依赖注入的更多相关文章

  1. 如何在 ASP.Net Web Forms 中使用依赖注入

    依赖注入技术就是将一个对象注入到一个需要它的对象中,同时它也是控制反转的一种实现,显而易见,这样可以实现对象之间的解耦并且更方便测试和维护,依赖注入的原则早已经指出了,应用程序的高层模块不依赖于低层模 ...

  2. ASP.NET Web API中的依赖注入

    什么是依赖注入 依赖,就是一个对象需要的另一个对象,比如说,这是我们通常定义的一个用来处理数据访问的存储,让我们用一个例子来解释,首先,定义一个领域模型如下: namespace Pattern.DI ...

  3. ASP.NET Core 过滤器中使用依赖注入

    如何给过滤器ActionFilterAttribute也用上构造函数注入呢? 一般自定义的过滤器直接用特性方式标识就能使用 [ContentFilter] 因为构造函数在使用的时候要求传参,然后我们可 ...

  4. ASP.NET Core 中的 依赖注入介绍

    ASP.NET Core 依赖注入 HomeController public class HomeController : Controller { private IStudentReposito ...

  5. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  6. ASP.NET Core 在 JSON 文件中配置依赖注入

    前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...

  7. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  8. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  9. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

随机推荐

  1. debian下使用Sphinx异常“Could not import extension sphinx.builders.linkcheck (exception: cannot import name SSLError)”的解决

    最近使用到Sphinx编译文档,出现如下异常: Extension error:Could not import extension sphinx.builders.linkcheck (except ...

  2. Smart3D系列教程7之 《手动配置S3C索引加载全部的瓦片数据》

    一.前言 迄今为止,Wish3D已经出品推出了6篇系列教程,从倾斜摄影的原理方法.采集照片的技巧.Smart3D各模块的功能应用.小物件的照片重建.大区域的地形重建到DSM及正射影像的处理生产,立足于 ...

  3. ql 判断 函数 存储过程是否存在的方法

    下面为您介绍sql下用了判断各种资源是否存在的代码,需要的朋友可以参考下,希望对您学习sql的函数及数据库能够有所帮助. 库是否存在 if exists(select * from master..s ...

  4. 浅谈Js闭包现象

    一.1.我们探究这个问题的时候如果按照正常的思维顺序,需要知道闭包是什么它是什么意思,但是这样做会让我们很困惑,了解这个问题我们需要知道它的来源,就是我们为什么要使用闭包,先不管它是什么意思!     ...

  5. java多态性,父类引用指向子类对象

    父类引用指向子类对象指的是: 例如父类Animal,子类Cat,Dog.其中Animal可以是类也可以是接口,Cat和Dog是继承或实现Animal的子类. Animal animal = new C ...

  6. android输入限制

    title: 2016-5-30 Android输入验证 tags: EditText,输入验证 --- 前言2 使用EditText让用户输入文字时,需要对输入验证.除过验证是否有效的逻辑不同,Ed ...

  7. (新年快乐)ABP理论学习之本地化(2016第一篇)

    返回总目录 本篇目录 应用语言 本地化资源 获取本地化文本 扩展本地化资源 最佳实践 应用语言 一个应用至少有一种UI语言,许多应用不止有一种语言.ABP为应用提供了一个灵活的本地化系统. 第一件事情 ...

  8. 比官方教程代码更简短的SignalR Server Broadcast示例

    SignalR是微软ASP.NET技术体系中的新成员. 在www.asp.net网站上的SignalR专区有一篇SignalR的入门级教程<Tutorial: Server Broadcast  ...

  9. golang reflect

    golang reflect go语言中reflect反射机制.详细原文:地址 接口值到反射对象 package main import ( "fmt" "reflect ...

  10. Qt5 Crash When Open File With QFileDialog

    问题描述 在使用Qt的QFileDialog这个类,来进行文件的打开和选择的时候, 就在调用的时候, 总是发生崩溃. 而且没有任何的提示性的信息. 而且崩溃的概率很高. 也有不崩溃的情况. 这个问题, ...