Reputation: 233
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
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
YourClass
and inject the IHttpContextAccessor
you created.YourClass
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
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