Skip to content

Commit

Permalink
Added Tests to the Caching package
Browse files Browse the repository at this point in the history
(#release-note: Added Tests to the Caching package #)
  • Loading branch information
Farshad DASHTI authored and Farshad DASHTI committed Oct 9, 2024
1 parent 8c8c02b commit 68bc138
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 8 deletions.
7 changes: 7 additions & 0 deletions DfE.CoreLibs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DfE.CoreLibs.Utilities.Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DfE.CoreLibs.Http.Tests", "src\Tests\DfE.CoreLibs.Http.Tests\DfE.CoreLibs.Http.Tests.csproj", "{69529D73-DD34-43A2-9D06-F3783F68F05C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DfE.CoreLibs.Caching.Tests", "src\Tests\DfE.CoreLibs.Caching.Tests\DfE.CoreLibs.Caching.Tests.csproj", "{807147EB-9B76-42F6-B249-A0F0CF3C3462}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -59,13 +61,18 @@ Global
{69529D73-DD34-43A2-9D06-F3783F68F05C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69529D73-DD34-43A2-9D06-F3783F68F05C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69529D73-DD34-43A2-9D06-F3783F68F05C}.Release|Any CPU.Build.0 = Release|Any CPU
{807147EB-9B76-42F6-B249-A0F0CF3C3462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{807147EB-9B76-42F6-B249-A0F0CF3C3462}.Debug|Any CPU.Build.0 = Debug|Any CPU
{807147EB-9B76-42F6-B249-A0F0CF3C3462}.Release|Any CPU.ActiveCfg = Release|Any CPU
{807147EB-9B76-42F6-B249-A0F0CF3C3462}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{07AE8F19-9566-4F0C-92E6-0A2BF122DCC9} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4}
{69529D73-DD34-43A2-9D06-F3783F68F05C} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4}
{807147EB-9B76-42F6-B249-A0F0CF3C3462} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01D11FBC-6C66-43E4-8F1F-46B105EDD95C}
Expand Down
2 changes: 0 additions & 2 deletions src/DfE.CoreLibs.Caching/Services/MemoryCacheService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;

namespace DfE.CoreLibs.Caching.Services
{
[ExcludeFromCodeCoverage]
public class MemoryCacheService(
IMemoryCache memoryCache,
ILogger<MemoryCacheService> logger,
Expand Down
13 changes: 7 additions & 6 deletions src/DfE.CoreLibs.Caching/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dotnet add package DfE.CoreLibs.Caching
public class GetPrincipalBySchoolQueryHandler(
ISchoolRepository schoolRepository,
IMapper mapper,
ICacheService cacheService)
ICacheService<IMemoryCacheType> cacheService)
: IRequestHandler<GetPrincipalBySchoolQuery, Principal?>
{
public async Task<Principal?> Handle(GetPrincipalBySchoolQuery request, CancellationToken cancellationToken)
Expand Down Expand Up @@ -64,12 +64,13 @@ In this example, the cache duration for `GetPrincipalBySchoolQueryHandler` is re
Here is the configuration for cache durations in the `appsettings.json` file:

```csharp
"CacheSettings": {
"DefaultDurationInSeconds": 60,
"Durations": {
"GetPrincipalBySchoolQueryHandler": 86400
"CacheSettings": {
"Memory": {
"DefaultDurationInSeconds": 60,
"Durations": {
"GetPrincipalBySchoolQueryHandler": 86400
}
}
}
```

This setup ensures that the `GetPrincipalBySchoolQueryHandler` cache duration is set to 24 hours (86400 seconds), while other handlers will use the default duration of 60 seconds if no specific duration is configured.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\DfE.CoreLibs.Caching\DfE.CoreLibs.Caching.csproj" />
<ProjectReference Include="..\..\DfE.CoreLibs.Testing\DfE.CoreLibs.Testing.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using DfE.CoreLibs.Caching.Helpers;

namespace DfE.CoreLibs.Caching.Tests.Helpers
{
public class CacheKeyHelperTests
{
[Fact]
public void GenerateHashedCacheKey_ShouldThrowArgumentException_WhenInputIsNullOrEmpty()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentException>(() => CacheKeyHelper.GenerateHashedCacheKey(string.Empty));
Assert.Throws<ArgumentException>(() => CacheKeyHelper.GenerateHashedCacheKey((string)null!));
}

[Fact]
public void GenerateHashedCacheKey_ShouldReturnConsistentHash_WhenGivenSameInput()
{
// Arrange
var input = "test-input";

// Act
var result1 = CacheKeyHelper.GenerateHashedCacheKey(input);
var result2 = CacheKeyHelper.GenerateHashedCacheKey(input);

// Assert
Assert.Equal(result1, result2);
}

[Fact]
public void GenerateHashedCacheKey_ShouldThrowArgumentException_WhenInputCollectionIsNullOrEmpty()
{
// Arrange, Act & Assert
Assert.Throws<ArgumentException>(() => CacheKeyHelper.GenerateHashedCacheKey((IEnumerable<string>)null!));
Assert.Throws<ArgumentException>(() => CacheKeyHelper.GenerateHashedCacheKey(new List<string>()));
}

[Fact]
public void GenerateHashedCacheKey_ShouldReturnDifferentHashes_ForDifferentInputs()
{
// Arrange
var input1 = "input-1";
var input2 = "input-2";

// Act
var result1 = CacheKeyHelper.GenerateHashedCacheKey(input1);
var result2 = CacheKeyHelper.GenerateHashedCacheKey(input2);

// Assert
Assert.NotEqual(result1, result2);
}

[Fact]
public void GenerateHashedCacheKey_ForCollection_ShouldReturnConsistentHash_WhenGivenSameInputs()
{
// Arrange
var inputs = new List<string> { "input-1", "input-2", "input-3" };

// Act
var result1 = CacheKeyHelper.GenerateHashedCacheKey(inputs);
var result2 = CacheKeyHelper.GenerateHashedCacheKey(inputs);

// Assert
Assert.Equal(result1, result2);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using AutoFixture;
using DfE.CoreLibs.Caching.Services;
using DfE.CoreLibs.Caching.Settings;
using DfE.CoreLibs.Testing.AutoFixture.Attributes;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;

namespace DfE.CoreLibs.Caching.Tests.Services
{
public class MemoryCacheServiceTests
{
private readonly IFixture _fixture;
private readonly IMemoryCache _memoryCache;
private readonly ILogger<MemoryCacheService> _logger;
private readonly IOptions<CacheSettings> _options;
private readonly MemoryCacheService _cacheService;
private readonly MemoryCacheSettings _cacheSettings;

public MemoryCacheServiceTests()
{
_fixture = new Fixture();
_memoryCache = Substitute.For<IMemoryCache>();
_logger = Substitute.For<ILogger<MemoryCacheService>>();

_cacheSettings = new MemoryCacheSettings { DefaultDurationInSeconds = 5, Durations = new Dictionary<string, int> { { "TestMethod", 10 } } };
var settings = new CacheSettings { Memory = _cacheSettings };
_options = Options.Create(settings);

_cacheService = new MemoryCacheService(_memoryCache, _logger, _options);
}

[Theory]
[CustomAutoData()]
public async Task GetOrAddAsync_ShouldReturnCachedValue_WhenCacheKeyExists(string cacheKey, string methodName)
{
// Arrange
var cachedValue = _fixture.Create<string>();
_memoryCache.TryGetValue(cacheKey, out Arg.Any<object>()!).Returns(x =>
{
x[1] = cachedValue;
return true;
});

// Act
var result = await _cacheService.GetOrAddAsync(cacheKey, () => Task.FromResult(cachedValue), methodName);

// Assert
Assert.Equal(cachedValue, result);
_logger.Received(1).Log(
LogLevel.Information,
Arg.Any<EventId>(),
Arg.Is<object>(v => v.ToString()!.Contains($"Cache hit for key: {cacheKey}")),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()!);
}

[Theory]
[CustomAutoData()]
public async Task GetOrAddAsync_ShouldFetchAndCacheValue_WhenCacheKeyDoesNotExist(string cacheKey, string methodName)
{
// Arrange
var expectedValue = _fixture.Create<string>();
_memoryCache.TryGetValue(cacheKey, out Arg.Any<object>()).Returns(false);

// Act
var result = await _cacheService.GetOrAddAsync(cacheKey, () => Task.FromResult(expectedValue), methodName);

// Assert
Assert.Equal(expectedValue, result);
_memoryCache.Received(1).Set(cacheKey, expectedValue, TimeSpan.FromSeconds(_cacheSettings.DefaultDurationInSeconds));
_logger.Received(1).Log(
LogLevel.Information,
Arg.Any<EventId>(),
Arg.Is<object>(v => v.ToString()!.Contains($"Cache miss for key: {cacheKey}")),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()!);
_logger.Received(1).Log(
LogLevel.Information,
Arg.Any<EventId>(),
Arg.Is<object>(v => v.ToString()!.Contains($"Cached result for key: {cacheKey}")),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()!);
}

[Theory]
[CustomAutoData()]
public void Remove_ShouldRemoveValueFromCache_WhenCalled(string cacheKey)
{
// Act
_cacheService.Remove(cacheKey);

// Assert
_memoryCache.Received(1).Remove(cacheKey);
_logger.Received(1).Log(
LogLevel.Information,
Arg.Any<EventId>(),
Arg.Is<object>(v => v.ToString()!.Contains($"Cache removed for key: {cacheKey}")),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()!);
}
}
}

0 comments on commit 68bc138

Please sign in to comment.