关于ZAKER 融媒体解决方案 合作 加入

在测试过程中如何在 Startup.cs 中伪造声明的 .

CocoaChina 11-11

我想为我的 Asp .net 核心应用程序编写集成测试 , 但我不希望我的测试使用某些服务的真实实现 .

public class Startup{ public void ConfigureServices ( IServiceCollection services ) { ... services.AddTransient<IExternalService,ExternalService> ( ) ; ... } public void Configure ( IApplicationBuilder app, IHostingEnvironment env ) { ... }}public interface IExternalService{ bool Verify ( int id ) ;}public class ExternalService : IExternalService{ public bool Verify ( int id ) { //Implemetation is here. //I want to fake this implemetation during testing. }} [ Fact ] public void TestCase ( ) { //Stub out service var myExtService = new Mock<IExternalService> ( ) ; //Setup response by stub myExtService .Setup ( p => p.Verify ( It.IsAny<int> ( ) ) ) .Returns ( false ) ; var host = new WebHostBuilder ( ) .UseStartup<Startup> ( ) .ConfigureServices ( ( services ) => { //Setup injection services.AddTransient<IExternalService> ( ( a ) => { return myExtService.Object; } ) ; } ) ; var server = new TestServer ( host ) ; var client = server.CreateClient ( ) ; var response = client.GetAsync ( "/" ) .Result; var responseString = response.Content.ReadAsStringAsync ( ) .Result; Assert.Contains ( "Your service returned: False", responseString ) ;}

测试案例中的当前注入设置不起作用 , 因为 ExternalService 是通过模拟注入的 .

但是 , 当我删除服务时 , 测试将通过 .AddTransient< IExternalService,ExternalService> ;; 从启动 . 最有可能在以后调用 Startup 中的一个 , 并且该类中的所有设置都被应用程序首选 . 在测试中设置某些依赖关系时 , 我必须使用哪些选项 , 但要使用其他在启动中声明的依赖关系?更新

> 应用程序代码应不了解测试 .

> 测试应注意:

> ( 弱类型 ) 端点 - 如果此更改发生变化 , 则测试应失败

> IExternalService 接口

> 测试不应该在乎应用程序是否具有剃须刀页面或使用 mvc, 还是在端点与 IExternalService 之间如何连接应用程序 .

> 测试不必设置或配置应用程序 ( 除了对 IExternalService 进行存根操作 ) 即可使其正常运行 .

> 我知道仍然必须创建 WebHostBuilder, 但是我的观点是 , 在测试案例中 , 配置应该是最低限度的 , 而大多数配置仍应在应用程序端进行描述 .

最佳答案

因此 , 经过数小时的研究 , 我找到了解决方案 .

我找不到单独使用内置依赖项注入解决方案的方法 , 所以我选择了第三方 DI 解决方案 -Autofac

想法是使用 WebHostBuilder ( 声明为主程序 ) 并添加必要的选项 , 以便在测试过程中伪造某些服务 .

我学到的东西:

> 如果您将 " 启动 " 用作主机 , 则使用 " 启动 ". 它将在 host.ConfigureServices ( ) 之后创建

> 您不能将诸如主机之类的东西注入 Startup.UseStartup< Startup> ( new Dependency ( ) )

> 但是 , 如果您在 host.ConfigureServices ( services => services.AddTransient< IDependency,MyDependency> ( ) ) 中已注册依赖项 , 则将在创建 Startup 并使用构造函数 public Startup ( IDependencydependency ) 创建 Startup 之前解决该问题 .

我的应用程序方面:

public class Program{ public static void Main ( string [ ] args ) { CreateWebHost ( args ) .Build ( ) .Run ( ) ; } public static IWebHostBuilder CreateWebHost ( string [ ] args ) => WebHost.CreateDefaultBuilder ( args ) .ConfigureServices ( ( services ) => { //Setup autofac. services.AddAutofac ( ) ; //Register module dependency that Startup requires. services.AddTransient<Module, MyAutofacModule> ( ) ; ////It would a bit cleaner to use autofac to setup Startup dependency, ////but dependency did not get resolved for Startup. //services.AddAutofac ( ( builder ) => //{ // builder.RegisterModule ( new AutofacModule ( ) ) ; //} ) ; } ) .UseStartup<Startup> ( ) ;}public class MyAutofacModule : Module{ protected override void Load ( ContainerBuilder builder ) { //Register all application dependencies in this module. builder.Register ( ( c ) => new ExternalService ( ) ) .As<IExternalService> ( ) ; }}public class Startup{ private Module applicationDIModule; public Startup ( Module applicationDIModule ) { this.applicationDIModule = applicationDIModule; } public void ConfigureServices ( IServiceCollection services ) { //We can add build-in services such as mvc and authorization, //but I would not use Add ( Transient/Scoped/Singleton ) here. //You should register domain specific dependecies in MyAutofacModule, //since it will be added after this method call. services.AddMvc ( ) ; } //This method is called after ConfigureServices ( refer to Autofac link ) . public void ConfigureContainer ( ContainerBuilder builder ) { //We will register injected module. builder.RegisterModule ( applicationDIModule ) ; } public void Configure ( IApplicationBuilder app, IHostingEnvironment env ) { if ( env.IsDevelopment ( ) ) { app.UseDeveloperExceptionPage ( ) ; } app.UseMvcWithDefaultRoute ( ) ; }}

测试用例:

public class IntegrationTests{ [ Fact ] public void TestCase ( ) { //Create and setup moq object as usual. var service = new Mock<IExternalService> ( ) ; service .Setup ( p => p.Verify ( It.IsAny<int> ( ) ) ) .Returns ( false ) ; //Bundle moq objects together for registration. var attachFakes = new Action<ContainerBuilder> ( ( builder ) => { builder.Register ( c => service.Object ) ; } ) ; //Use host builder that application uses. var host = Program.CreateWebHost ( new string [ ] { } ) .UseContentRoot ( GetContentRoot ( ) ) //Adjust content root since testproject.csproj is not in same folder as application.csproj .ConfigureServices ( ( services ) => { //We re-configure Module registration, //so Startup is injected with our TestModule. services.AddTransient<Module> ( ( a ) => { return new TestModule ( attachFakes ) ; } ) ; } ) ; //Create server to use our host and continue to test. var server = new TestServer ( host ) ; var client = server.CreateClient ( ) ; var response = client.GetAsync ( "/" ) .Result; var responseString = response.Content.ReadAsStringAsync ( ) .Result; Assert.Contains ( "External service result: False", responseString ) ; } private static string GetContentRoot ( ) { var current = Directory.GetCurrentDirectory ( ) ; var parent = Directory.GetParent ( current ) .Parent.Parent.Parent; return Path.Combine ( parent.FullName, "src" ) ; }}public class TestModule : MyAutofacModule{ private Action<ContainerBuilder> attachFakes; public TestModule ( Action<ContainerBuilder> attachFakes ) { this.attachFakes = attachFakes; } protected override void Load ( ContainerBuilder builder ) { //We register everything in MyAutoFacModule before adding our fakes. base.Load ( builder ) ; //We add fakes and everything that is re-registered here will be used instead. attachFakes.Invoke ( builder ) ; }}

尽管感觉有点脆弱 , 但是我仍然更喜欢此解决方案 , 而不是 @ODawg 建议的解决方案 . 他的解决方案可以工作 , 但我认为将来添加新的测试用例时会造成麻烦 .

以上内容由"CocoaChina"上传发布 查看原文
相关标签 测试过程app

觉得文章不错,微信扫描分享好友

扫码分享