【问题标题】:How can I stub/mock services in ASP.NET Core tests如何在 ASP.NET Core 测试中存根/模拟服务
【发布时间】:2017-07-21 02:04:39
【问题描述】:

我想测试一些属于 ASP.NET Web Api 项目的类。我不需要通过 TestServer 进行请求-响应集成测试(尽管它们很好),但我想让我的测试尽可能接近“真实的东西”。因此,我想使用在 Startup 中添加的服务来解决我的类,但在测试的基础上通过存根/模拟来更改其中的一些(有些测试需要模拟,而另一些则不需要)。

在过去,当 ASP.NET 没有内部依赖注入框架时,这很容易做到。所以我只需调用一个类,将所有依赖项注册到一个容器中,然后为每个测试创建子容器,将一些依赖项更改为模拟,就是这样。

我尝试过这样的事情:

    var host = A.Fake<IHostingEnvironment>();
    var startup = new Startup(host);
    var services = new ServiceCollection();
    //Add stubs here
    startup.ConfigureServices(services);
    var provider = services.BuildServiceProvider();
    provider.GetService<IClientsHandler>();

这似乎可行,但我不想为每个测试创建整个启动基础架构。我想创建一次,然后为每个测试创建“子容器”或“子范围”。可能吗?基本上我正在寻找一种在 Startup 之外修改服务的方法。

【问题讨论】:

    标签: .net asp.net-web-api asp.net-core .net-core


    【解决方案1】:

    documentation 中已详细介绍了这一切。

    对于集成测试,你使用TestServer 类并给它一个Startup 类(不必是实时启动,也可以是StartupIntegrationTest 或使用Configure{Envrionment Name here} / ConfigureServices{Envrionment Name here} 方法启动。

     var server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
        // this would cause it to use StartupIntegrationTest class or ConfigureServicesIntegrationTest / ConfigureIntegrationTest methods (if existing)
        // rather than Startup, ConfigureServices and Configure 
        .UseEnvironment("IntegrationTest")); 
    

    要访问服务提供者,请执行

    var server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
        .UseEnvironment("IntegrationTest")); 
    var controller = server.Host.Services.GetService<MyService>();
    

    对于单元测试,您根本不应该使用IServiceCollection/IServiceProvider,只需模拟接口并注入它们。

    【讨论】:

    • 但我不想手动注入模拟接口。我想使用服务解析类,并且我想在解析类之前为某些接口指定模拟。
    • 阅读整个答案。最后一段仅用于单元测试,大部分答案是关于进行集成测试以及它们应该如何完成。这就是 TestServer 的用途,它的用途是 Startup。虽然您不必使用请求,但如第二个代码示例中所述,您可以从测试服务器实例访问 IServiceProvider
    • 所以你认为我们应该为每个测试创建一个新的TestServer?我只是不知道如何为每个测试创建一个 TestServer 并更改依赖项(对于某些测试我需要模拟,对于其他我不需要)。
    • 你不应该改变它们,集成是关于测试一个整体的特定配置,你可以为每个 startup{environmentname} 类(或 configure{environment} 方法)配置一次。即,您将在“StartupIntegrationTest”类中​​将 DbContext 配置从生产替换为内存数据库。使用 xunit 时,您可以使用类或集合夹具在多个单元测试中重用测试服务器。当您想单独测试一个类(没有真正的依赖项)时,这就是单元测试的用途。
    • 单元测试和集成测试之间没有二进制划分。中间有一个巨大的灰色地带。如果我有完整的集成测试,但在某些情况下我想模拟一些依赖项(即不发送真正的电子邮件),我的测试仍然是集成测试。
    【解决方案2】:

    在配置 HttpConfiguration 时,可以通过创建自定义 IHttpControllerActivator 来为每个请求创建子范围。

    这适用于 OWIN,但转换为 .Net Core 应该非常简单: https://gist.github.com/jt000/eef096a2341471856e8a86d06aaec887

    重要的部分是在该范围内创建一个范围和控制器......

    var scope = _provider.CreateScope();
    request.RegisterForDispose(scope);
    
    var controller = scope.ServiceProvider.GetService(controllerType) as IHttpController;
    

    ...并覆盖默认的IHttpControllerActivator...

    config.Services.Replace(typeof (IHttpControllerActivator), new ServiceProviderControllerActivator(parentActivator, provider));
    

    现在您可以通过 IServiceProvider 使用作用域依赖注入添加要创建的控制器...

    services.AddScoped<ValuesController>((sp) => new ValuesController(sp.GetService<ISomeCustomService>()));
    

    要在单元测试中测试 ValuesController,我建议使用 Moq 框架之类的东西来模拟服务接口中的方法。例如:

    var someCustomService = Mock.Of<ISomeCustomService>(s => s.DoSomething() == 3);
    var sut = new ValuesController(someCustomService);
    
    var result = sut.Get();
    
    Assert.AreEqual(result, new [] { 3 });
    

    【讨论】:

    • 我没有时间去看看,但它看起来很有希望。谢谢!
    【解决方案3】:

    如果您想为您的xUnit 单元测试使用相同的Web API Core 控制器和DI 基础设施(在这种情况下我将它们称为集成测试),我建议移动TestServerHttpClient 实现xUnit IClassFixture 的基类的上下文。

    在这种情况下,您将使用所有服务和 DI 测试 API,并在您的真实 Web API Core 中配置:

    public class TestServerDependent : IClassFixture<TestServerFixture>
    {
        private readonly TestServerFixture _fixture;
        public TestServer TestServer => _fixture.Server;
        public HttpClient Client => _fixture.Client;
    
        public TestServerDependent(TestServerFixture fixture)
        {
            _fixture = fixture;
        }
    
        protected TService GetService<TService>()
            where TService : class
        {
            return _fixture.GetService<TService>();
        }
    }
    
    public class TestServerFixture : IDisposable
    {
        public TestServer Server { get; }
        public HttpClient Client { get; }
    
        public TestServerFixture()
        {
            // UseStaticRegistration is needed to workaround AutoMapper double initialization. Remove if you don't use AutoMapper.
            ServiceCollectionExtensions.UseStaticRegistration = false;
    
            var hostBuilder = new WebHostBuilder()
                .UseEnvironment("Testing")
                .UseStartup<Startup>();
    
            Server = new TestServer(hostBuilder);
            Client = Server.CreateClient();
        }
    
        public void Dispose()
        {
            Server.Dispose();
            Client.Dispose();
        }
    
        public TService GetService<TService>()
            where TService : class
        {
            return Server?.Host?.Services?.GetService(typeof(TService)) as TService;
        }
    }
    

    然后你可以从这个类派生来测试控制器的动作:

    public class ValueControllerTests : TestServerDependent
    {
        public ValueControllerTests(TestServerFixture fixture)
            : base(fixture)
        {
        }
    
        [Fact]
        public void Returns_Ok_Response_When_Requested()
        {
            var responseMessage = Client.GetAsync("/api/value").Result;
            Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
        }
    }
    

    您还可以测试DI 服务:

    public class MyServiceTests : TestServerDependent
    {
        public MyServiceTests(TestServerFixture fixture)
            : base(fixture)
        {
        }
    
        [Fact]
        public void ReturnsDataWhenServiceInjected()
        {
            var service = GetService<IMyService>();
            Assert.NotNull(service);
    
            var data = service.GetData();
            Assert.NotNull(data);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-23
      • 2018-08-16
      • 2021-09-18
      • 2020-03-04
      • 2015-07-07
      • 1970-01-01
      相关资源
      最近更新 更多