Jagjit Singh
Jagjit Singh

Reputation: 751

How to mock out call to service in an integration test?

Let us say I want to perform an integration test on an API controller method looking like this:

 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));
        }
    }

My integration test looks like this(I have just started to implement it, the first test is not fully completed):

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}");
    }
}

In the API controller method I want to mock out the call to _fileStorageService.GetFileUrlWithAccessKey(id);

I have tried to mock out the call to __fileStorageService, by mocking out the interface IFileStorageService

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();
    }
}

But I dont think the call to var result = await _fileStorageService.GetFileUrlWithAccessKey(id); is being mocked out correct in my TestServerFixture class, because my test code keep going into this code and I am getting error because I have not provided parameters to fileStorageService. What can I do in this scenario to mock out the call to a service completely, so we dont go into that code?

Upvotes: 1

Views: 8740

Answers (3)

Trung
Trung

Reputation: 69

In my case, I already tried services.TryAddScoped(...) but it could not solved, then I changed to services.AddScoped(...) and it worked.

.ConfigureServices(services =>

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

Btw, thank you, your solution worked

Upvotes: 0

Jagjit Singh
Jagjit Singh

Reputation: 751

I have found out after help from my project lead:

In the TestServerFixture this sentence can be replaced:

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

With:

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

In order to make the mocks functional you need to change your ConfigureServices method in the startup class. Instead of calling AddScoped, AddInstance, Add or AddTransient you need to call the TryAdd... variant of the class that you want to replace in your tests. (Source: https://fizzylogic.nl/2016/07/22/running-integration-tests-for-asp-net-core-apps/)

So that means, in my case the startup.cs class will need to call TryAddScoped instead of AddScoped like this.

services.TryAddScoped<IFileStorageService, AzureBlobStorageService>();

Upvotes: 3

ste-fu
ste-fu

Reputation: 7482

Just because you create a Fake service doesn't mean you mocking framework knows what to return for a method call.

I'm not that familiar with FakeItEasy but I think you want something like this:

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

where fakeResult is what you want to return for your test.

Upvotes: 0

Related Questions