-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
Aghogho Bernard edited this page May 6, 2026
·
1 revision
The simplest approach is to register CacheWeave.InMemory in your test project — it uses IMemoryCache and requires no external dependencies.
using CacheWeave.Core.Extensions;
using CacheWeave.InMemory.Extensions;
using Microsoft.Extensions.DependencyInjection;
public static class TestServiceCollectionExtensions
{
public static IServiceCollection AddCacheWeaveForTesting(
this IServiceCollection services)
{
services.AddCacheWeave(options =>
{
options.KeyVersion = "test";
options.DefaultExpiry = TimeSpan.FromMinutes(5);
options.EnableMetrics = false;
});
services.AddCacheWeaveInMemory();
return services;
}
}Use it in xUnit:
public class ProductServiceTests
{
private readonly IServiceProvider _sp;
public ProductServiceTests()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddCacheWeaveForTesting();
services.AddScoped<ProductService>();
// ... register other dependencies
_sp = services.BuildServiceProvider();
}
[Fact]
public async Task GetAllAsync_SecondCall_ReturnsCachedResult()
{
var sut = _sp.GetRequiredService<ProductService>();
var first = await sut.GetAllAsync();
var second = await sut.GetAllAsync();
// Both calls return the same object reference (from cache)
Assert.Same(first, second);
}
}Use WebApplicationFactory<TProgram> to spin up the full ASP.NET Core pipeline with an in-memory cache:
public class ProductsControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public ProductsControllerTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace the Redis provider with InMemory for tests
services.AddCacheWeaveInMemory();
});
}).CreateClient();
}
[Fact]
public async Task GetAll_SecondRequest_ReturnsCachedResponse()
{
var first = await _client.GetAsync("/api/products?page=1");
var second = await _client.GetAsync("/api/products?page=1");
first.EnsureSuccessStatusCode();
second.EnsureSuccessStatusCode();
// Both responses should have identical bodies
var body1 = await first.Content.ReadAsStringAsync();
var body2 = await second.Content.ReadAsStringAsync();
Assert.Equal(body1, body2);
}
[Fact]
public async Task Create_InvalidatesListCache()
{
// Populate the cache
await _client.GetAsync("/api/products?page=1");
// Mutate
var cmd = new { name = "New Product", categoryId = Guid.NewGuid() };
var post = await _client.PostAsJsonAsync("/api/products", cmd);
post.EnsureSuccessStatusCode();
// The next GET should re-fetch (cache was evicted)
var afterCreate = await _client.GetAsync("/api/products?page=1");
afterCreate.EnsureSuccessStatusCode();
}
}Use Moq or NSubstitute to mock ICacheWeaveService in unit tests where you want to assert cache interactions without a real provider:
using NSubstitute;
public class ProductControllerTests
{
[Fact]
public async Task GetAll_CacheHit_DoesNotCallDatabase()
{
var cache = Substitute.For<ICacheWeaveService>();
var db = Substitute.For<IProductRepository>();
cache.GetOrSetAsync<List<ProductDto>>(
Arg.Any<string>(),
Arg.Any<Func<Task<List<ProductDto>?>>>(),
Arg.Any<TimeSpan?>(),
Arg.Any<CancellationToken>())
.Returns(new List<ProductDto> { new() { Id = Guid.NewGuid(), Name = "Steel Beam" } });
var controller = new ProductsController(cache, db);
var result = await controller.GetAll();
Assert.IsType<OkObjectResult>(result);
await db.DidNotReceive().GetAllAsync(Arg.Any<CancellationToken>());
}
}Inject ICacheProvider directly in integration tests to inspect cache state:
public class CacheIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public CacheIntegrationTests(WebApplicationFactory<Program> factory)
=> _factory = factory;
[Fact]
public async Task GetAll_PopulatesCache()
{
var app = _factory.WithWebHostBuilder(b =>
b.ConfigureServices(s => s.AddCacheWeaveInMemory()));
var client = app.CreateClient();
var cache = app.Services.GetRequiredService<ICacheProvider>();
await client.GetAsync("/api/products?page=1");
// Key format: baseKey:version:queryParams
var cached = await cache.GetAsync<object>("products:list:test:page=1");
Assert.NotNull(cached);
}
}[Fact]
public async Task Delete_EvictsCache()
{
var app = _factory.WithWebHostBuilder(b =>
b.ConfigureServices(s => s.AddCacheWeaveInMemory()));
var client = app.CreateClient();
var cache = app.Services.GetRequiredService<ICacheProvider>();
// Populate cache
await client.GetAsync("/api/products?page=1");
// Delete a product
var id = Guid.NewGuid();
await client.DeleteAsync($"/api/products/{id}");
// Cache should be cleared
var cached = await cache.GetAsync<object>("products:list:test:page=1");
Assert.Null(cached);
}To test the underlying logic without caching interference, register a no-op provider:
public class NoOpCacheProvider : ICacheProviderInner
{
public Task<string?> GetAsync(string key, CancellationToken ct = default)
=> Task.FromResult<string?>(null);
public Task SetAsync(string key, string value, TimeSpan? expiry = null, CancellationToken ct = default)
=> Task.CompletedTask;
public Task RemoveAsync(string key, CancellationToken ct = default)
=> Task.CompletedTask;
public Task RemoveByPrefixAsync(string prefix, CancellationToken ct = default)
=> Task.CompletedTask;
}
// In test setup:
services.AddCacheWeave();
services.AddSingleton<ICacheProviderInner, NoOpCacheProvider>();Every request will be a cache miss and every write will be discarded — the action logic runs on every call.