Skip to content

Testing Flurl Code

Testing of Flurl code involves testing the behavior of your code that uses the Flurl library to make HTTP calls. Flurl is a fluent, portable, testable HTTP client library for .NET.

To effectively test your flurl code,kindly note the following:

  • Flurl provides a class called HttpTest that allows you to simulate HTTP calls and set up expected responses. This means you can test your code's behavior under different scenarios without making actual network requests.

  • To set up responses you can use methods like RespondWith, RespondWithJson, and SimulateException on an HttpTest instance to define how it should respond when a certain URL is called. This allows you to test how your code handles different responses.

  • The HttpTest class also provides methods like ShouldHaveCalled to verify that an HTTP call to a certain URL was made. This helps ensure your code is making the correct HTTP calls.

  • By using HttpTest, you can isolate your tests from external dependencies. This makes your tests more reliable, as they are not affected by the availability or behavior of external services.

  • Flurl's testing features can be used in conjunction with any .NET testing framework, such as xUnit.net, NUnit, or MSTest. This allows you to write tests for your Flurl code in the same way you would write tests for any other code.

Please check the example below to see how flurl code is tested.

c#
public class AccountLookupServiceTests : IDisposable
{
    private readonly AccountLookupService _accountLookupService;
    private readonly ILogger<AccountLookupService> _logger;
    private readonly HttpTest _httpTest;
private readonly string _testurl = "http://test.com";
 
    public AccountLookupServiceTests()
    {
        _httpTest = new HttpTest();
        _logger = Substitute.For<ILogger<AccountLookupService>>();
        var authenticationConfigStore = Substitute.For<IAuthenticationConfigStoreService>();
        var redisCacheRepository = Substitute.For<IMultiRedisHostCacheRepository>();
        redisCacheRepository
            .GetDb(Arg.Any<string>(), Arg.Any<string>())
            .Returns(Substitute.For<IDatabase>());
 
        _accountLookupService = new AccountLookupService(
            authenticationConfigStore,
            redisCacheRepository,
            _logger
        );
    }
 
 
    [Fact]
    public async Task LookupCustomerInformation_Should_ReturnAccountLookupResponse_When_ApiResponseIsSuccessful()
    {
        // Arrange
        var phoneNumber = "123456789";
        var email = "[email protected]";
        var expected = new AccountLookupResponse()
        {
            Code = "200",
            Data = new AccountLookupData
            {
                MobileNumber = phoneNumber,
                Email = email,
                TokenData = new Dictionary<string, string>
                {
                    { "email", email },
                    { "phone", phoneNumber },
                }
            },
            Message = "success"
        };
 
        _httpTest.RespondWithJson(expected);
 
        // Act
        var result = await _accountLookupService.LookupCustomerInformation(_testurl, phoneNumber);
 
        // Assert
        using (new AssertionScope())
        {
            _httpTest.ShouldHaveCalled(_testurl);
            result.Should().NotBeNull();
            result.Should().BeEquivalentTo(expected);
        }
    }
 
    [Fact]
    public async Task LookupCustomerInformation_Should_ReturnNull_When_ApiResponseIsBadRequest()
    {
        // Arrange
        _httpTest.RespondWith("Bad Request", 400);
 
        // Act
        var result = await _accountLookupService.LookupCustomerInformation(_testurl, "123456789");
 
        // Assert
        using (new AssertionScope())
        {
            _httpTest.ShouldHaveCalled(_testurl);
            result.Should().BeNull();
        }
    }
 
    [Fact]
    public async Task LookupCustomerInformation_Should_ReturnNull_When_ApiResponseIsNotFound()
    {
        // Arrange
        _httpTest.RespondWith("Resource not found", 404);
 
        // Act
        var result = await _accountLookupService.LookupCustomerInformation(_testurl, "123456789");
 
        // Assert
        _httpTest.ShouldHaveCalled($"{_testurl}/*");
        result.Should().BeNull();
    }
 
    [Fact]
    public async Task LookupCustomerInformation_Should_ReturnAccountLookupResponse_When_ApiResponseIsForbidden()
    {
        // Arrange
        var expectedResponse = new AccountLookupResponse
        {
            Code = "403",
            Message = "account lookup for 123456789 Not Allowed. Error Details"
        };
        _httpTest.RespondWithJson(expectedResponse, 403);
 
        // Act
        var result = await _accountLookupService.LookupCustomerInformation(_testurl, "123456789");
 
        // Assert
        using (new AssertionScope())
        {
            _httpTest.ShouldHaveCalled($"{_testurl}/*");
            result.Should().NotBeNull();
            result.Code.Should().Be(expectedResponse.Code);
            result.Message.Should().Contain(expectedResponse.Message);
        }
    }
 
    [Fact]
    public async Task LookupCustomerInformation_Should_ReturnNull_When_ApiCallThrowsException()
    {
        // Arrange
        _httpTest.SimulateException(new DependencyResolutionException("could not get data"));
 
        // Act
        var result = await _accountLookupService.LookupCustomerInformation(_testurl, "123456789");
 
        // Assert
        result.Should().BeNull();
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    protected virtual void Dispose(bool disposing)
    {
        _httpTest.Dispose();
    }
}
  • AccountLookupServiceTests(): This is the constructor of the AccountLookupServiceTests class. It initializes the _httpTest,_logger, authenticationConfigStore, and redisCacheRepository fields. It also sets up the redisCacheRepository to return a mock IDatabase when its GetDb method is called. Finally, it initializes the _accountLookupService field with a new instance of AccountLookupService, passing in the mock objects.

  • LookupCustomerInformation_Should_ReturnAccountLookupResponse_When_ApiResponseIsSuccessful(): This is a test method that verifies the behavior of the LookupCustomerInformation method when the API response is successful. It sets up a mock API response, calls the method, and asserts that the method returns the expected result.

  • LookupCustomerInformation_Should_ReturnNull_When_ApiResponseIsBadRequest(): This is a test method that verifies the behavior of the LookupCustomerInformation method when the API response is a bad request. It sets up a mock API response, calls the method, and asserts that the method returns null.

  • LookupCustomerInformation_Should_ReturnNull_When_ApiResponseIsNotFound(): This is a test method that verifies the behavior of the LookupCustomerInformation method when the API response is not found. It sets up a mock API response, calls the method, and asserts that the method returns null.

  • LookupCustomerInformation_Should_ReturnAccountLookupResponse_When_ApiResponseIsForbidden(): This is a test method that verifies the behavior of the LookupCustomerInformation method when the API response is forbidden. It sets up a mock API response, calls the method, and asserts that the method returns the expected result.

  • LookupCustomerInformation_Should_ReturnNull_When_ApiCallThrowsException(): This is a test method that verifies the behavior of the LookupCustomerInformation method when the API call throws an exception. It simulates an exception during the API call, calls the method, and asserts that the method returns null.

  • Dispose(): This is the Dispose method from the IDisposable interface. It calls the Dispose(bool) method with true and suppresses the finalization of the object, which prevents the garbage collector from calling the object's finalizer, if it has one.

  • Dispose(bool): This is an overloaded version of the Dispose method. It disposes of the _httpTest object if the disposing parameter is true. This method is typically used in cases where an object needs to free unmanaged and managed resources.