ASP.NET5单元测试中使用依赖注入

王朝学院·作者佚名  2016-08-28
窄屏简体版  字體: 小  |  中  |  大  |  超大  

相关博文:《asp.net5 使用 TestServer 进行单元测试》

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

namespaceCNBlogs.Ad.Application.Tests{publicclassAdTextServiceTest : BaseTest

{PRivateIAdTextService _adTextService;publicAdTextServiceTest(ITestOutputHelper output)

:base(output)

{

Bootstrapper.Startup.ConfigureMapper();

IUnitOfWork unitOfWork =newUnitOfWork(dbContext);

IMemcached memcached =newEnyimMemcached(null);

_adTextService =newAdTextService(newAdTextRepository(dbContext),newAdTextCreativeRepository(dbContext),newUserService(memcached),

unitOfWork,

memcached);

}

[Fact]publicasync TaskGetByAdTextsTest()

{varadTexts = await _adTextService.GetAdTexts();

Assert.NotNull(adTexts);

adTexts.ForEach(x => Console.WriteLine($"{x.Title}({x.Link})"));

}

}

}

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

namespaceCNBlogs.Ad.WebApi{publicclassStartup

{publicStartup(IHostingEnvironment env, IApplicationEnvironment appEnv)

{// Set up configuration sources.varbuilder =newConfigurationBuilder()

.AddJsonFile("appsettings.json")

.AddEnvironmentVariables();

Configuration = builder.Build();

}publicIConfigurationRoot Configuration {get;set; }// This method gets called by the runtime. Use this method to add services to the container.publicvoidConfigureServices(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 应用程序和单元测试项目,都可以使用它,减少代码的重复,比如我们可以进行下面设计:

namespaceCNBlogs.Ad.Bootstrapper{publicstaticclassStartup

{publicstaticvoidConfigure(thisIServiceCollection services,stringconnectionString)

{

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>();

}publicstaticvoidConfigureMapper()

{

Mapper.CreateMap<CNBlogs.Ad.Domain.Entities.AdText, AdTextDTO>();

}

}

}

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

publicvoidConfigureServices(IServiceCollection services)

{

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

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

namespaceCNBlogs.Ad.BaseTests{publicclassBaseTest

{protectedreadonlyITestOutputHelper output;protectedIServiceProvider provider;publicBaseTest(ITestOutputHelper output)

{

var connectionString ="";varservices =newServiceCollection();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 的作用就是获取注入的服务对象,我们下面会用到:

namespaceCNBlogs.Ad.Repository.Tests{publicclassAdTextServiceTest : BaseTest

{privateIAdTextService _adTextService;publicAdTextServiceTest(ITestOutputHelper output)

:base(output)

{

_adTextService = provider.GetService<IAdTextService>();

}

[Fact]publicasync TaskGetByAdTextsTest()

{varadTexts = await _adTextService.GetAdTexts();

Assert.NotNull(adTexts);

adTexts.ForEach(x => Console.WriteLine($"{x.Title}({x.Link})"));

}

}

}

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

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航