cowefe
cowefe

Reputation: 233

How to write a test for the method which is a part of larger service?

I have met a following problem: I have to write a test for a method which is a part of a service, but does not make use of other parts of said service, except for a two methods (which is called Remove and is presented below).

Method I need to write a test for takes in a cookie name. Then, based on that cookie name it gets from dictionary a consent category said cookie belongs to. After that there is IF statement which uses the HasCookieConsent method and determines if cookie should be removed, or not. If yes, it is removed by Remove method.

public void UpdateCookiesAccordingToConsent(string cookie)
{
    var cookiesConsentType = _httpOnlyCookies.FirstOrDefault(x => x.Key ==     
cookie).Value;

    if (!HasCookieConsent(cookiesConsentType) && _httpOnlyCookies.ContainsKey(cookie))
        {
            Remove(cookie);     
        }
}

Cookie categories are taken from dictionary:

private readonly Dictionary<string, CookiesConsentType> _httpOnlyCookies = new Dictionary<string, CookiesConsentType>()
{
    { CookieNames.VisitorCookieName,  CookiesConsentType.Statistic },
    { CookieNames.GoogleAnalyticsTrackingCookieName, CookiesConsentType.Statistic },
    { CookieNames.TaxonomyVisitorCookieName, CookiesConsentType.Statistic },
    { CookieNames.MarketoMunchkinTrackingCookieName, CookiesConsentType.Marketing },
};

Remove method:

public void Remove(string cookie)
{
    if (_httpContextAccessor.HttpContext == null)
    {
        return;
    }
    var options = new CookieOptions
    {
        HttpOnly = true,
            Secure = _httpContextAccessor.HttpContext.Request.IsHttps,
            Expires = DateTime.Now.AddDays(-1),
    };
    _httpContextAccessor.HttpContext.Response.Cookies.Append(cookie, string.Empty, options);
}

HasCookieConsent method:

private bool HasCookieConsent(CookiesConsentType consentType)
{
try
{
    var hasConsentCookie = _httpContextAccessor?.HttpContext?.Request?.Cookies?.ContainsKey("CookieConsent") ?? false;
        if (!hasConsentCookie)
        {
            return false;
        }

    var cookie = _httpContextAccessor.HttpContext.Request.Cookies["CookieConsent"] ?? string.Empty;

        if (string.IsNullOrWhiteSpace(cookie))
        {
            return false;
        }

    var cookieConsent = JsonConvert.DeserializeObject<CookieConsent>(cookie) ?? new CookieConsent();
        return consentType switch
        {
            CookiesConsentType.Preferences => cookieConsent.Preferences,
            CookiesConsentType.Marketing => cookieConsent.Marketing,
            CookiesConsentType.Statistic => cookieConsent.Statistics,
            CookiesConsentType.Necessary => cookieConsent.Necessary,
            _ => false,
        };
    }
    catch (Exception ex)
    {
        _logger.LogError("Could not deserialize cookie: {Exception}", ex);
        return false;
    }
}

Any tips on how to do it? I am using xUnit.

Upvotes: 0

Views: 68

Answers (2)

Scott Hannen
Scott Hannen

Reputation: 29212

In order to test this class as-is you'll need to create an HttpContext with a request that contains the cookies you want. Then you'll need an IHttpContextAccessor which returns that HttpContext.

It looks like you're injecting the IHttpContextAccessor into your class, like this:

public class YourClass
{
    private IHttpContextAccessor _httpContextAccessor;

    public YourClass(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    // all your other methods
}

In your test you'll

  • Create an instance of YourClass and inject the IHttpContextAccessor you created.
  • Call the methods of YourClass
  • Assert that the response cookies are what you expect them to be.

In this case you don't need to create any mocks. You can use existing implementations.

Here's what a test would look like. It's vague because I'm not trying to follow the specifics of what your code does and I'm not sure what the expected result is supposed to be.

public void UpdateCookiesAccordingToConsent_does_whatever()
{
    // Create an HttpContext and add some request cookies to it
    var httpContext = new DefaultHttpContext();
    httpContext.Request.Cookies = new RequestCookieCollection(new Dictionary<string, string>()
    {
        { "cookieName", "cookieValue" },
        { "cookieName2", "cookieValue2" }
    });

    // Create an HttpContextAccessor that returns your HttpContext
    var contextAccessor = new HttpContextAccessor
    {
        HttpContext = httpContext
    };

    // Create an instance of your class. When it gets the current HttpContext
    // it will get the one you created.
    var testSubject = new YourClass(contextAccessor);

    // Call some method on your class.
    testSubject.UpdateCookiesAccordingToConsent("whatever");

    // Presumably executing that method has done something to the cookies.
    // Assert that the cookies in httpContext contain what you expect.
}

Here's another approach. Going this route depends upon your coding style.

If the logic in one of the methods becomes very complex or you find yourself having to reuse it, you could move it into a separate method, even an extension in another class.

Here's an example:

public static bool HasCookieConsent(this IRequestCookieCollection requestCookies)
{
    try
    {
        var hasConsentCookie = requestCookies?.ContainsKey("CookieConsent") ?? false;
        if (!hasConsentCookie)
        {
            return false;
        }

        var cookie = requestCookies["CookieConsent"] ?? string.Empty;

        if (string.IsNullOrWhiteSpace(cookie))
        {
            return false;
        }

        var cookieConsent = JsonConvert.DeserializeObject<CookieConsent>(cookie) ?? new CookieConsent();
        return consentType switch
        {
            CookiesConsentType.Preferences => cookieConsent.Preferences,
            CookiesConsentType.Marketing => cookieConsent.Marketing,
            CookiesConsentType.Statistic => cookieConsent.Statistics,
            CookiesConsentType.Necessary => cookieConsent.Necessary,
            _ => false,
        };
    }
    catch (Exception ex)
    {
        _logger.LogError("Could not deserialize cookie: {Exception}", ex);
        return false;
    }
}

The method is modified not to depend on IHttpContextAccessor or HttpContext. It doesn't care about those things. It only cares about the cookies, so those are passed as a parameter.

It's easy to test this method by creating a RequestCookiesCollection (like in the previous example), passing it to this method, and asserting that the method returns true or false as expected.

That's a coding preference. Some people prefer to have more private methods and test them all through the public ones. In other cases we might extract methods into their own classes for testing. In each case I'd go with whatever makes your tests easier to write. I'm not selling one or the other, just presenting options.

Upvotes: 2

Jacob
Jacob

Reputation: 389

try something like this.

[Test]
public void UpdateCookiesAccordingToConsent_CallsRemove_WhenHasCookieConsentIsFalseAndHttpOnlyCookiesContainsCookie()
{
    // Arrange
    var cookie = "testCookie";
    var cookiesConsentType = CookiesConsentType.Statistic;
    var httpOnlyCookies = new Dictionary<string, CookiesConsentType>()
    {
        { cookie, cookiesConsentType }
    };

    var service = new CookieService(httpOnlyCookies);
    service.HasCookieConsent(false);

    // Act
    service.UpdateCookiesAccordingToConsent(cookie);

    // Assert
    service.Remove(cookie);
}

Upvotes: 0

Related Questions