如何在ASP.NET Core测试中存根/模拟服务

问题描述 投票:14回答:3

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

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

我试过这样的事情:

    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
3个回答
4
投票

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

在这种情况下,你将测试API所有的服务和DI配置你真正的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);
    }
}

3
投票

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

这适用于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 });

2
投票

这一切都在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,只需模拟接口并注入它们。

© www.soinside.com 2019 - 2024. All rights reserved.