相关博文:《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})"));
}
}
}

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

最新文章

  1. 前端移动App开发环境搭建
  2. 每个程序员都会的35个jQuery小技巧!
  3. jsp页面传参大汇总-转帖收藏
  4. 2015暑假多校联合---Zero Escape(变化的01背包)
  5. fetch the words from url
  6. NPOI--操作Excel之利器(一)
  7. vsftp实现ftps加密传输数据
  8. Java实现Http服务器(四)
  9. @property的特性
  10. base64编码问题 需要对每个参数URL编码
  11. 【Struts2】新建一个Struts2工程,初步体验MVC
  12. 用户输入密码隐藏之getpass的使用
  13. Codeforces Round #406 (Div. 1)
  14. HTTP的缓存策略
  15. 5.sql2008分组与嵌套
  16. Codeforces 776E The Holmes Children
  17. 建立你第一个 Outlook Add-in
  18. c++ remove_if
  19. centos7 --ngnix 常用命令:
  20. javascript浮点数转换成整数三种方法

热门文章

  1. 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记.
  2. Ubuntu创建桌面快捷方式
  3. python安装完毕后,提示找不到ssl模块的解决步骤
  4. OHSCE_V0.1.22 Beta,跨平台高可靠性通信框架
  5. wamp2.5 局域网无法访问问题
  6. Filter 数组过滤函数精解示例
  7. Amoeba -- 阿里巴巴工程师的开源项目之一陈思儒
  8. HTML的两三事
  9. C#与Java对比学习:数据类型、集合类、栈与队列、迭达、可变参数、枚举
  10. 推荐升级ASP.NET Web API 2