Home > Blockchain >  How can I refactor my C# code to be easily testable?
How can I refactor my C# code to be easily testable?

Time:12-25

I have written below code:

public RequestValidationResultBase Validate(ParRequest parRequest, Client client)
{
    if (client is null) throw new ArgumentNullException(nameof(client));

    // validate whether client is allowed to use PAR
    if (!IsStringContainedInArray(GrantTypes.AuthorizationCode, client.GrantTypes.Select(p => p.GrantType).ToArray()))
        return new(new InvalidGrantType());

    if (parRequest is null || HasAnyNullProperty(parRequest, nameof(ParRequest.Prompt), nameof(ParRequest.ApiScope)))
        return new(new InvalidRequest());

    // validate whether requested callback uri is valid for given client
    if (!IsStringContainedInArray(parRequest.CallbackUri, client.Callbacks.Select(p => p.Uri).ToArray()))
        return new(new InvalidCallbackUri());

    // validate whether requested api scopes are valid for given client
    // NOTE that we want to allow requests without api scopes (some clients may need user identity only)
    if (!string.IsNullOrEmpty(parRequest.ApiScope)
        && !IsFirstArrayContainedInSecondArray(parRequest.ApiScope.Split(' '), client.AllowedApiScopes.Select(p => p.Name).ToArray()))
        return new(new InvalidApiScope());

    // we want to ignore refresh_token api scope when prompt "consent" is not present
    if (!string.IsNullOrEmpty(parRequest.ApiScope) && parRequest.ApiScope.Split(' ').Contains("offline_access")
        && (string.IsNullOrEmpty(parRequest.Prompt) || !parRequest.Prompt.Split(' ').Contains(PromptTypes.Consent)))
        parRequest.ApiScope = parRequest.ApiScope.Replace("offline_access", string.Empty).Trim();

    // validate whether requested identity resources are valid for given client
    if (!IsFirstArrayContainedInSecondArray(parRequest.IdentityResource.Split(' '),
        client.AllowedIdentityResources.Select(p => p.Name).ToArray()))
        return new(new InvalidIdentityResource());

    // validate whether requested prompt is valid
    if (!PromptTypes.ValidatePrompt(parRequest.Prompt)) return new(new InvalidPrompt());

    if (!IsStringBase64Url(parRequest.State, 32, 160)) return new(new InvalidState());
    if (!IsStringBase64Url(parRequest.Nonce, 32)) return new(new InvalidState());
    if (!IsStringBase64Url(parRequest.CodeChallenge, 32)) return new(new InvalidCodeChallenge());
    if (!IsStringBase64Url(parRequest.AuthChallenge, 32)) return new(new InvalidAuthChallenge());

    return new() { Valid = true };
}

And I don't have any idea how can I write unit tests for this.

If I want to test e.g. result when prompt is invalid, then all steps above prompt validation must be valid.

I can't mock specific if conditions and even if I would pack all these if condition into small private methods then I still can't mock private methods. So how can I write unit tests for this code or how should I refactor this code to be easily testable?

Or maybe the only solution is to create a ParRequest object that gets to a certain point in the validation?

CodePudding user response:

I can't mock specific if conditions

You don't need to mock the ifs, each test case can pass a progressively more valid input with the same setup for all the test cases.

CodePudding user response:

Each cluster of if statements used for validation could be moved into public methods inside a validation class.

You can then test each validation method rigorously with various test cases and then test the original method can just have one or two general test cases.

e.g. something like this:

public void LongMethodWithLotsOfValidation()
{
    if (_age < 10)
        //do something
        
    if (_height > 6)
        //do something
        
    if (_experience > 10)
        //do something
    
    if (_money > 100)
        //do something
}

Can be refactored to something like this:

public static class Validation
{
    private const AgeLimit = 10;
    private const MinHeight = 6;
    private const MinYearsExperience = 10;
    private const MinMoney = 100;
    
    public static bool IsAgeLowEnough(int age)
    {
        return age < AgeLimit;
    }
    
    public static bool IsHeightGreatEnough(int age)
    {
        return height > MinHeight;
    }
    
    public static bool IsExperienceEnough(int age)
    {
        return experience > MinYearsExperience;
    }
    
    public static bool IsMoneyEnough(int age)
    {
        return money > MinMoney;
    }
}

And the validation class can be used like so:

public void ImprovedMethod()
{
    if (Validation.IsAgeLowEnough(_age))
        //do something
        
    if (Validation.IsHeightGreatEnough(_height))
        //do something
        
    if (Validation.IsExperienceEnough(_experience))
        //do something
        
    if (Validation.IsMoneyEnough(_money))
        //do something
}
  • Related