【问题标题】:How to mock out call to service in an integration test?如何在集成测试中模拟对服务的调用?
【发布时间】:2020-06-25 16:20:20
【问题描述】:

假设我想对如下所示的 API 控制器方法执行集成测试:

 public async Task<IActionResult> Get(Guid id)
    {
        try
        {
            if (id == Guid.Empty)
            {
                return new BadRequestObjectResult("Id is not valid");
            }

            var result = await _fileStorageService.GetFileUrlWithAccessKey(id);

            if (result == null)
            {
                return new NotFoundObjectResult("Could not find any file with given id");
            }

            var document = new Document()
            {
                Url = result
            };

            return Ok(document);
        }
        catch (StorageException storageException)
        {
            switch (storageException.RequestInformation.HttpStatusCode)
            {
                case 404:
                    return Json(StatusCode(404));
                default:
                    return Json(StatusCode(500));
            }
        }
        catch (Exception)
        {
            return Json(StatusCode(500));
        }
    }

我的集成测试是这样的(我刚刚开始实现,第一次测试还没有完全完成):

public class DocumentsControllerTest : IClassFixture<TestServerFixture>
{
    private readonly HttpClient Client;

    public DocumentsControllerTest(TestServerFixture fixture)
    {
        Client = fixture.Client;
    }

    [Fact]
    public async Task Get_WhenCalledNotExistingFileId_ShouldReturn404StatusCode()
    {
        var nonExistentId = Guid.NewGuid();

        var response = await Client.GetAsync($"/documents/{nonExistentId}");
    }
}

在 API 控制器方法中,我想模拟对 _fileStorageService.GetFileUrlWithAccessKey(id); 的调用

我试图通过模拟接口 IFileStorageService 来模拟对 __fileStorageService 的调用

public class TestServerFixture
{
    /// <summary>
    /// Test fixture that can be used by test classes where we want an HttpClient
    /// that can be shared across all tests in that class.
    /// </summary>
    public HttpClient Client { get; set; }
    private readonly TestServer _server;

    public TestServerFixture()
    {
        var webHostBuilder = new WebHostBuilder()
            .UseEnvironment("UnitTest")
            .UseStartup<Startup>()
            .ConfigureServices(services =>
            {
                services.TryAddScoped(serviceProvider => A.Fake<IFileStorageService>());
            });

        _server = new TestServer(webHostBuilder);
        Client = _server.CreateClient();
    }

    public void Dispose()
    {
        Client.Dispose();
        _server.Dispose();
    }
}

但我不认为在我的TestServerFixture 类中对var result = await _fileStorageService.GetFileUrlWithAccessKey(id); 的调用是正确的,因为我的测试代码不断进入此代码并且我收到错误,因为我没有向fileStorageService 提供参数。在这种情况下我可以做些什么来完全模拟对服务的调用,这样我们就不用进入那个代码了?

【问题讨论】:

  • 集成测试中需要模拟什么?
  • 我们想从端到端测试应用程序,而不是数据库,所以我们的测试基本上是集成测试和单元测试的混合体。最主要的是,混合动力正在帮助我们实现我们的目标。
  • 你能不能把所有的集成点都指向真实的测试实例,这会让你更有信心而不是嘲笑它。
  • 是的,也许这将是我们将来会考虑的事情。谢谢你的建议!

标签: c# asp.net integration-testing xunit fakeiteasy


【解决方案1】:

在项目负责人的帮助下,我发现了:

TestServerFixture这句话可以替换:

 .ConfigureServices(services =>
        {
            services.TryAddScoped(serviceProvider => A.Fake<IFileStorageService>());
        });

与:

 .ConfigureServices(services =>
            {
                services.AddScoped(serviceProvider => A.Fake<IFileStorageService>());
            });

为了使模拟功能发挥作用,您需要更改启动类中的 ConfigureServices 方法。 您需要调用要在测试中替换的类的 TryAdd... 变体,而不是调用 AddScoped、AddInstance、Add 或 AddTransient。 (来源:https://fizzylogic.nl/2016/07/22/running-integration-tests-for-asp-net-core-apps/

这意味着,在我的例子中,startup.cs 类需要像这样调用 TryAddScoped 而不是 AddScoped。

services.TryAddScoped<IFileStorageService, AzureBlobStorageService>();

【讨论】:

    【解决方案2】:

    仅仅因为您创建了一个 Fake 服务并不意味着您模拟框架知道为方法调用返回什么。

    我对 FakeItEasy 不太熟悉,但我认为您想要这样的东西:

    var fakeFileStorageService = A.Fake<IFileStorageService>();
    A.CallTo(() => fakeFileStorageService.GetFileUrlWithAccessKey(A<Guid>.Ignored))
           .Returns(fakeResult);
    

    其中 fakeResult 是您要为测试返回的内容。

    【讨论】:

    • 问题中提到,测试没有完全实现。现在我知道如何模拟对服务的调用,所以下一步是提供一个 fakeResult。无论如何感谢您的提醒:)
    【解决方案3】:

    就我而言,我已经尝试过services.TryAddScoped(...),但无法解决,然后我更改为services.AddScoped(...),它成功了。

    .ConfigureServices(services =>
    
        {
            services.AddScoped(serviceProvider => A.Fake<IFileStorageService>());
        });
    

    顺便说一句,谢谢,您的解决方案有效

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-20
      • 1970-01-01
      • 1970-01-01
      • 2019-12-07
      • 2023-03-31
      相关资源
      最近更新 更多