Skip to content

Commit 190e6ce

Browse files
committed
CachedRepositoryTests and small refactoring
1 parent 01a14f0 commit 190e6ce

File tree

4 files changed

+145
-12
lines changed

4 files changed

+145
-12
lines changed

LinkDotNet.Blog.Infrastructure/Persistence/CachedRepository.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,13 @@ public CachedRepository(IRepository<T> repository, IMemoryCache memoryCache)
2121

2222
public async Task<T> GetByIdAsync(string id)
2323
{
24-
if (memoryCache.TryGetValue(id, out T item))
24+
if (!memoryCache.TryGetValue(id, out T value))
2525
{
26-
return item;
26+
value = await repository.GetByIdAsync(id);
27+
memoryCache.Set(id, value);
2728
}
2829

29-
var fromDb = await repository.GetByIdAsync(id);
30-
memoryCache.Set(id, fromDb);
31-
return fromDb;
30+
return value;
3231
}
3332

3433
public async Task<IPagedList<T>> GetAllAsync(
@@ -38,24 +37,26 @@ public async Task<IPagedList<T>> GetAllAsync(
3837
int page = 1,
3938
int pageSize = int.MaxValue)
4039
{
41-
var key = $"{page}-{pageSize}";
40+
var key = $"{filter?.Body}-{orderBy?.Body}-{descending}-{page}-{pageSize}";
4241
return await memoryCache.GetOrCreate(key, async e =>
4342
{
4443
e.SetOptions(new MemoryCacheEntryOptions
4544
{
46-
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1),
45+
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
4746
});
4847
return await repository.GetAllAsync(filter, orderBy, descending, page, pageSize);
4948
});
5049
}
5150

5251
public async Task StoreAsync(T entity)
5352
{
53+
memoryCache.Set(entity.Id, entity, TimeSpan.FromHours(1));
5454
await repository.StoreAsync(entity);
5555
}
5656

5757
public async Task DeleteAsync(string id)
5858
{
59+
memoryCache.Remove(id);
5960
await repository.DeleteAsync(id);
6061
}
6162
}

LinkDotNet.Blog.Infrastructure/Persistence/Sql/Repository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public Repository(BlogDbContext blogDbContext)
2020

2121
public async Task<TEntity> GetByIdAsync(string id)
2222
{
23-
return await blogDbContext.Set<TEntity>().SingleOrDefaultAsync(b => b.Id == id);
23+
return await blogDbContext.Set<TEntity>().AsNoTracking().SingleOrDefaultAsync(b => b.Id == id);
2424
}
2525

2626
public async Task<IPagedList<TEntity>> GetAllAsync(
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using System.Threading.Tasks;
4+
using FluentAssertions;
5+
using LinkDotNet.Blog.Domain;
6+
using LinkDotNet.Blog.Infrastructure.Persistence;
7+
using LinkDotNet.Blog.TestUtilities;
8+
using Microsoft.Extensions.Caching.Memory;
9+
using Moq;
10+
using X.PagedList;
11+
using Xunit;
12+
13+
namespace LinkDotNet.Blog.UnitTests.Infrastructure.Persistence
14+
{
15+
public class CachedRepositoryTests
16+
{
17+
private readonly Mock<IRepository<BlogPost>> repositoryMock;
18+
private readonly CachedRepository<BlogPost> sut;
19+
20+
public CachedRepositoryTests()
21+
{
22+
repositoryMock = new Mock<IRepository<BlogPost>>();
23+
sut = new CachedRepository<BlogPost>(repositoryMock.Object, new MemoryCache(new MemoryCacheOptions()));
24+
}
25+
26+
[Fact]
27+
public async Task ShouldGetFromCacheWhenLoaded()
28+
{
29+
var blogPost = new BlogPostBuilder().Build();
30+
repositoryMock.Setup(r => r.GetByIdAsync("id")).ReturnsAsync(blogPost);
31+
var firstCall = await sut.GetByIdAsync("id");
32+
33+
var secondCall = await sut.GetByIdAsync("id");
34+
35+
firstCall.Should().Be(secondCall);
36+
firstCall.Should().Be(blogPost);
37+
repositoryMock.Verify(r => r.GetByIdAsync("id"), Times.Once);
38+
}
39+
40+
[Fact]
41+
public async Task ShouldGetAllFromCacheWhenLoaded()
42+
{
43+
var blogPost = new BlogPostBuilder().Build();
44+
repositoryMock.Setup(r => r.GetAllAsync(
45+
It.IsAny<Expression<Func<BlogPost, bool>>>(),
46+
It.IsAny<Expression<Func<BlogPost, object>>>(),
47+
It.IsAny<bool>(),
48+
It.IsAny<int>(),
49+
It.IsAny<int>()))
50+
.ReturnsAsync(new PagedList<BlogPost>(new[] { blogPost }, 1, 1));
51+
var firstCall = await sut.GetAllAsync();
52+
53+
var secondCall = await sut.GetAllAsync();
54+
55+
firstCall.Count.Should().Be(1);
56+
secondCall.Count.Should().Be(1);
57+
repositoryMock.Verify(
58+
r => r.GetAllAsync(
59+
It.IsAny<Expression<Func<BlogPost, bool>>>(),
60+
It.IsAny<Expression<Func<BlogPost, object>>>(),
61+
It.IsAny<bool>(),
62+
It.IsAny<int>(),
63+
It.IsAny<int>()),
64+
Times.Once);
65+
}
66+
67+
[Fact]
68+
public async Task ShouldNotCacheWhenParameterDifferent()
69+
{
70+
SetupRepository();
71+
await sut.GetAllAsync();
72+
await sut.GetAllAsync(p => p.IsPublished);
73+
await sut.GetAllAsync(p => p.IsPublished, p => p.Likes);
74+
await sut.GetAllAsync(
75+
p => p.IsPublished,
76+
p => p.Likes,
77+
false);
78+
await sut.GetAllAsync(
79+
p => p.IsPublished,
80+
p => p.Likes,
81+
false,
82+
2);
83+
await sut.GetAllAsync(
84+
p => p.IsPublished,
85+
p => p.Likes,
86+
false,
87+
2,
88+
30);
89+
90+
repositoryMock.Verify(
91+
r => r.GetAllAsync(
92+
It.IsAny<Expression<Func<BlogPost, bool>>>(),
93+
It.IsAny<Expression<Func<BlogPost, object>>>(),
94+
It.IsAny<bool>(),
95+
It.IsAny<int>(),
96+
It.IsAny<int>()),
97+
Times.Exactly(6));
98+
}
99+
100+
[Fact]
101+
public async Task ShouldUpdateCacheOnStore()
102+
{
103+
var blogPost = new BlogPostBuilder().Build();
104+
blogPost.Id = "id";
105+
repositoryMock.Setup(r => r.GetByIdAsync("id")).ReturnsAsync(blogPost);
106+
await sut.GetByIdAsync("id");
107+
var update = new BlogPostBuilder().WithTitle("new").Build();
108+
blogPost.Update(update);
109+
await sut.StoreAsync(blogPost);
110+
111+
var latest = await sut.GetByIdAsync("id");
112+
113+
latest.Title.Should().Be("new");
114+
}
115+
116+
[Fact]
117+
public async Task ShouldDelete()
118+
{
119+
await sut.DeleteAsync("id");
120+
121+
repositoryMock.Verify(r => r.DeleteAsync("id"), Times.Once);
122+
}
123+
124+
private void SetupRepository()
125+
{
126+
var blogPost = new BlogPostBuilder().Build();
127+
repositoryMock.Setup(r => r.GetAllAsync(
128+
It.IsAny<Expression<Func<BlogPost, bool>>>(),
129+
It.IsAny<Expression<Func<BlogPost, object>>>(),
130+
It.IsAny<bool>(),
131+
It.IsAny<int>(),
132+
It.IsAny<int>()))
133+
.ReturnsAsync(new PagedList<BlogPost>(new[] { blogPost }, 1, 1));
134+
}
135+
}
136+
}

LinkDotNet.Blog.UnitTests/LinkDotNet.Blog.UnitTests.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@
4444
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
4545
</ItemGroup>
4646

47-
<ItemGroup>
48-
<Folder Include="Infrastructure\Persistence" />
49-
</ItemGroup>
50-
5147
<ItemGroup>
5248
<Compile Remove="Web\Shared\Services\LocalStorageServiceTests.cs" />
5349
</ItemGroup>

0 commit comments

Comments
 (0)