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

时间:2017-03-01 16:25:09

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

我想测试一些属于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以外的服务。

3 个答案:

答案 0 :(得分:4)

如果您希望为npm install单元测试使用相同的Web API Core控制器和DI基础结构(在这种情况下我称之为集成测试),我建议将xUnitTestServer上下文移动到实现HttpClient IClassFixture的基类。

在这种情况下,您将使用所有服务测试xUnit并在您的真实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

答案 1 :(得分:3)

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

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

重要的部分是用于创建Scope&amp;控制器在那个范围内......

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 :(得分:2)

documentation中包含了所有内容。

对于集成测试,您使用TestServer类并为其提供Startup类(不必是实时启动,也可以是StartupIntegrationTest或启动{{1} } / 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,只需模拟接口并注入它们。