如何在集成测试中模拟对服务的调用?

时间:2017-08-21 08:49:52

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

我们假设我想对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();
    }
}

但我不认为var result = await _fileStorageService.GetFileUrlWithAccessKey(id);TestServerFixture的调用在我的{{1}}类中被嘲笑是正确的,因为我的测试代码会继续使用此代码而我收到错误因为我没有提供参数fileStorageService。在这种情况下我可以做什么来完全模拟对服务的调用,所以我们不会进入该代码?

3 个答案:

答案 0 :(得分: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>();

答案 1 :(得分:0)

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

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

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

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

答案 2 :(得分:0)

在我的情况下,我已经尝试过services.TryAddScoped(...),但无法解决,然后我改成了services.AddScoped(...),就可以了。

.ConfigureServices(services =>

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

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