1

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?

Jagjit Singh
  • 661
  • 9
  • 16
  • What's the need of mocking in an *Integration test*? – Rahul Aug 21 '17 at 08:50
  • We want to test the application from end to end, but not the database, so our test is basically a hybrid between integration test and unit test. The main thing is that the hybrid is helping us for our purpose. – Jagjit Singh Aug 21 '17 at 09:05
  • can you just not just point all integration points at real test instances, this would give you more confidence instead of mocking it. – Kevin Smith Aug 21 '17 at 09:22
  • Yes, maybe that will be something we will consider in the future. Thank you for the advise! – Jagjit Singh Aug 21 '17 at 09:25

3 Answers3

3

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>();
Jagjit Singh
  • 661
  • 9
  • 16
0

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.

ste-fu
  • 6,879
  • 3
  • 27
  • 46
  • As mentioned in the question, the test was not fully implemented. Now I know how to mock out the call to the service, so the next step is provide a fakeResult. Thanks for the reminder anyway :) – Jagjit Singh Aug 21 '17 at 09:27
0

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

Trung
  • 61
  • 4