Search code examples
c#unit-testingnunitmstestxunit

ASP.NET Core - Unit test service methods that make external API calls


I recently started learning about automated testing and I'm pretty comfortable with integration tests from a controller level. I have an ASP.NET Core Web API that does verification checks and it calls the below methods in the service. I'm struggling with figuring out how to unit test them as they make calls to an external API.

How do I unit test these? Is there supposed to be mocking involved or…? I'd appreciate any helpful input and practical examples if possible (as in what would I Arrange, Act and Assert).

public VerificationResponse VerifyDetails(VerificationRequest verificationRequest)
{
    try
    {
        var token = GetVerificationAuthenticationToken();

        if (!String.IsNullOrWhiteSpace(token))
        {
            var verificationUrl = _Configuration.GetSection("Verification:VerificationSettings:Url").Value;
            var timeout = int.Parse(_Configuration.GetSection("RestAPIClientTimeout").Value);

            var client = new RestClient(verificationUrl);
            client.Timeout = timeout;
            var request = new RestRequest(Method.POST);
            var body = JsonSerializer.Serialize<VerificationRequest>(verificationRequest);

            request.AddHeader("Authorization", $"Bearer {token}");
            request.AddHeader("Content-Type", "application/json");
            request.AddParameter("application/json", body, ParameterType.RequestBody);
            var verificationResponse = client.Execute<VerificationResponse>(request);

            if (!verificationResponse .IsSuccessful)
            {
                throw new ResponseException(verificationResponse.Content);
            }

            return verificationResponse.Data;
        }
        else
        {
            throw new ResponseException("The verification access token is empty");
        }
    }
    catch (Exception ex)
    {
        _Logger.LogError(ex, ex.Message);
        throw;
    }
}
public string GetVerificationAuthenticationToken()
{
    try
    {
        var clientID = _Configuration.GetSection("Verification:TokenSettings:ClientID").Value;
        var username = _Configuration.GetSection("Verification:TokenSettings:Username").Value;
        var tokenUrl = _Configuration.GetSection("Verification:TokenSettings:Url").Value;
        var scope = _Configuration.GetSection("Verification:TokenSettings:Scope").Value;
        var password = _Configuration.GetSection("Verification:TokenSettings:Password").Value;
        var grantType = _Configuration.GetSection("Verification:TokenSettings:GrantType").Value;
        var timeout = int.Parse(_Configuration.GetSection("RestAPIClientTimeout").Value);

        var client = new RestClient(tokenUrl);
        client.Timeout = timeout;
        var request = new RestRequest(Method.POST);
        request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
        request.AddParameter("client_id", clientID);
        request.AddParameter("username", username);
        request.AddParameter("password", password);
        request.AddParameter("grant_type", grantType);
        request.AddParameter("scope", scope);
        var response = client.Execute<VerificationTokenResponse>(request);

        if (!response.IsSuccessful)
        {
            throw new ResponseException(response.Content);
        }

        return response.Data.AccessToken;
    }
    catch (Exception ex)
    {
        _Logger.LogError(ex, ex.Message);
        throw;
    }
}

Solution

  • Anything that calls an external web API should be moved into its own class. That new class should implement an interface. The controller would receive an object implementing this interface as a constructor or method parameter.

    In this case, you want your controllers to be very simple, and move most of this logic into another class.

    This is a pretty big refactoring job. The code to get an authentication token, and the code calling the web API to get the verification response should all be moved to another class. Basicall the controller is reduced to:

    public class YourController : Controller
    {
        private IWebApiClient api;
    
        public YourController(IWebApiClient api)
        {
            this.api = api;
        }
    
        // other controller methods...
    
        public VerificationResponse VerifyDetails(VerificationRequest verificationRequest)
        {
            try
            {
                var verificationResponse = api.Verify(verificationRequest);
    
                if (!verificationResponse.IsSuccessful)
                {
                    throw new ResponseException(verificationResponse.Content);
                }
    
                return verificationResponse.Data;
            }
            catch (Exception ex)
            {
                _Logger.LogError(ex, ex.Message);
                throw;
            }
        }
    }
    

    You need an interface and an implementing class:

    public interface IWebApiClient
    {
        VerificationResponse Verify(VerificationRequest verificationRequest);
    }
    
    public class WebApiClient : IWebApiClient
    {
        public VerificationResponse Verify(VerificationRequest verificationRequest)
        {
            // get token, make call, etc.
        }
    }
    

    In Services.cs, register an instance of WebApiClient to satisfy the need for an IWebApiClient interface.

    Now your unit tests can mock the response objects for IWebApiClient. You can write your own implementation if you want, or use a mocking library like Moq.