This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
In dit bericht zal ik beginnen met het toevoegen van Unit Testing voor deze site. Dit zal niet een volledige tutorial op Unit Testing, maar eerder een reeks berichten over hoe ik het toevoegen van Unit Testing aan deze site. In dit bericht test ik een aantal diensten door DbContext te bespotten; dit is om elke DB specifieke shennanigans te vermijden.
Unit Testing is een manier om individuele componenten van uw code afzonderlijk te testen. Dit is nuttig om een aantal redenen:
Er zijn een aantal andere soorten testen die u kunt doen. Hier zijn een paar:
Ik ga xUnit gebruiken voor mijn testen. Dit wordt standaard gebruikt in ASP.NET Core projecten. Ik ga ook Moq gebruiken om de DbContext te bespotten samen met
Ter voorbereiding hierop heb ik een Interface toegevoegd voor mijn DbContext. Dit is zodat ik de DbContext kan bespotten in mijn testen. Hier is de interface:
namespace Mostlylucid.EntityFramework;
public interface IMostlylucidDBContext
{
public DbSet<CommentEntity> Comments { get; set; }
public DbSet<BlogPostEntity> BlogPosts { get; set; }
public DbSet<CategoryEntity> Categories { get; set; }
public DbSet<LanguageEntity> Languages { get; set; }
public DatabaseFacade Database { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
Het is vrij eenvoudig, gewoon ontmaskeren van onze DBSets en de SaveChangesAsync methode.
I Niet doen. gebruik een repository patroon in mijn code. Dit komt omdat Entity Framework Core al een repository patroon is. Ik gebruik een servicelaag om te communiceren met de DbContext. Dit komt omdat ik de kracht van Entity Framework Core niet wil wegnemen.
We voegen dan een nieuwe klasse toe aan onze Mostlylucid.Test
project met een extensiemethode om onze zoekopdracht op te zetten:
public static class MockDbSetExtensions
{
public static Mock<DbSet<T>> CreateDbSetMock<T>(this IEnumerable<T> sourceList) where T : class
{
// Use the MockQueryable.Moq extension method to create the mock
return sourceList.AsQueryable().BuildMockDbSet();
}
// SetupDbSet remains the same, just uses the updated CreateDbSetMock
public static void SetupDbSet<T>(this Mock<IMostlylucidDBContext> mockContext, IEnumerable<T> entities,
Expression<Func<IMostlylucidDBContext, DbSet<T>>> dbSetProperty) where T : class
{
var dbSetMock = entities.CreateDbSetMock();
mockContext.Setup(dbSetProperty).Returns(dbSetMock.Object);
}
}
Je zult zien dat dit gebruik maakt van de MockQueryable.Moq
extensie methode om de mock te maken. Dat stelt dan onze IQueryable objecten en IAsyncQueryable objecten.
Een kernprincipe van Unit Testing is dat elke test een 'eenheid' van het werk moet zijn en niet afhankelijk moet zijn van het resultaat van een andere test (hierom bespotten we onze DbContext).
In onze nieuwe BlogServiceFetchTests
klasse zetten we onze testcontext in de constructeur op:
public BlogServiceFetchTests()
{
// 1. Setup ServiceCollection for DI
var services = new ServiceCollection();
// 2. Create a mock of IMostlylucidDbContext
_dbContextMock = new Mock<IMostlylucidDBContext>();
// 3. Register the mock of IMostlylucidDbContext into the ServiceCollection
services.AddSingleton(_dbContextMock.Object);
// Optionally register other services
services.AddScoped<IBlogService, EFBlogService>(); // Example service that depends on IMostlylucidDbContext
services.AddLogging(configure => configure.AddConsole());
services.AddScoped<MarkdownRenderingService>();
// 4. Build the service provider
_serviceProvider = services.BuildServiceProvider();
}
Ik heb dit behoorlijk zwaar becommentarieerd zodat je kunt zien wat er aan de hand is. We zijn bezig met het opzetten van een ServiceCollection
Dat is een verzameling van diensten die we kunnen gebruiken in onze tests. Wij maken dan een bespotting van ons IMostlylucidDBContext
en het in het register op te nemen. ServiceCollection
. Vervolgens registreren we alle andere diensten die we nodig hebben voor onze tests. De Voorzitter. - Aan de orde is het gecombineerd debat over ServiceProvider
die we kunnen gebruiken om onze diensten van te krijgen.
Ik begon met het toevoegen van een enkele test klasse, de bovengenoemde BlogServiceFetchTests
Klas. Dit is een testklas voor de Post het krijgen van methoden van mijn EFBlogService
Klas.
Elke test maakt gebruik van een gemeenschappelijke SetupBlogService
methode om een nieuwe bevolkte EFBlogService
object. Dit is zodat we de dienst afzonderlijk kunnen testen.
private IBlogService SetupBlogService(List<BlogPostEntity>? blogPosts = null)
{
blogPosts ??= BlogEntityExtensions.GetBlogPostEntities(5);
// Setup the DbSet for BlogPosts in the mock DbContext
_dbContextMock.SetupDbSet(blogPosts, x => x.BlogPosts);
// Resolve the IBlogService from the service provider
return _serviceProvider.GetRequiredService<IBlogService>();
}
Dit is een eenvoudige extensie klasse die geeft ons een aantal gepupilde BlogPostEntity
objecten. Dit is zodat we onze service kunnen testen met een aantal verschillende objecten.
public static List<BlogPostEntity> GetBlogPostEntities(int count, string? langName = "")
{
var langs = LanguageExtensions.GetLanguageEntities();
if (!string.IsNullOrEmpty(langName)) langs = new List<LanguageEntity> { langs.First(x => x.Name == langName) };
var langCount = langs.Count;
var categories = CategoryEntityExtensions.GetCategoryEntities();
var entities = new List<BlogPostEntity>();
var enLang = langs.First();
var cat1 = categories.First();
// Add a root post to the list to test the category filter.
var rootPost = new BlogPostEntity
{
Id = 0,
Title = "Root Post",
Slug = "root-post",
HtmlContent = "<p>Html Content</p>",
PlainTextContent = "PlainTextContent",
Markdown = "# Markdown",
PublishedDate = DateTime.ParseExact("2025-01-01T07:01", "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture),
UpdatedDate = DateTime.ParseExact("2025-01-01T07:01", "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture),
LanguageEntity = enLang,
Categories = new List<CategoryEntity> { cat1 }
};
entities.Add(rootPost);
for (var i = 1; i < count; i++)
{
var langIndex = (i - 1) % langCount;
var language = langs[langIndex];
var postCategories = categories.Take(i - 1 % categories.Count).ToList();
var dayDate = (i + 1 % 30 + 1).ToString("00");
entities.Add(new BlogPostEntity
{
Id = i,
Title = $"Title {i}",
Slug = $"slug-{i}",
HtmlContent = $"<p>Html Content {i}</p>",
PlainTextContent = $"PlainTextContent {i}",
Markdown = $"# Markdown {i}",
PublishedDate = DateTime.ParseExact($"2025-01-{dayDate}T07:01", "yyyy-MM-ddTHH:mm",
CultureInfo.InvariantCulture),
UpdatedDate = DateTime.ParseExact($"2025-01-{dayDate}T07:01", "yyyy-MM-ddTHH:mm",
CultureInfo.InvariantCulture),
LanguageEntity = new LanguageEntity
{
Id = language.Id,
Name = language.Name
},
Categories = postCategories
});
}
return entities;
}
U kunt zien dat alles wat dit doet is het retourneren van een aantal blog posts met Talen en Categorieën. We voegen echter altijd een 'root' object toe waardoor we in onze testen op een bekend object kunnen vertrouwen.
Elke test is ontworpen om één aspect van de resultaten van de posten te testen.
Bijvoorbeeld in de twee hieronder testen we simpelweg dat we alle berichten kunnen krijgen en dat we berichten per taal kunnen krijgen.
[Fact]
public async Task TestBlogService_GetBlogsByLanguage_ReturnsBlogs()
{
var blogService = SetupBlogService();
// Act
var result = await blogService.GetPostsForLanguage(language: "es");
// Assert
Assert.Single(result);
}
[Fact]
public async Task TestBlogService_GetAllBlogs_ReturnsBlogs()
{
var blogs = BlogEntityExtensions.GetBlogPostEntities(2);
var blogService = SetupBlogService(blogs);
// Act
var result = await blogService.GetAllPosts();
// Assert
Assert.Equal(2, result.Count());
}
Een belangrijk concept in Unit testing is 'testfout' waarbij je vaststelt dat je code faalt op de manier die je verwacht.
In de onderstaande tests testen we eerst of onze paging code werkt zoals verwacht. We testen dan dat als we meer pagina's vragen dan we hebben, we een leeg resultaat krijgen (en geen fout).
[Fact]
public async Task TestBlogServicePagination_GetBlogsByCategory_ReturnsBlogs()
{
var blogPosts = BlogEntityExtensions.GetBlogPostEntities(10, "en");
var blogService = SetupBlogService(blogPosts);
// Act
var result = await blogService.GetPagedPosts(2, 5);
// Assert
Assert.Equal(5, result.Posts.Count);
}
[Fact]
public async Task TestBlogServicePagination_GetBlogsByCategory_FailsBlogs()
{
var blogPosts = BlogEntityExtensions.GetBlogPostEntities(10, "en");
var blogService = SetupBlogService(blogPosts);
// Act
var result = await blogService.GetPagedPosts(10, 5);
// Assert
Assert.Empty(result.Posts);
}
Dit is een simpele start van onze Unit Testing. In de volgende post voegen we testen voor meer diensten en eindpunten toe. We zullen ook kijken hoe we onze eindpunten kunnen testen met behulp van Integratie Testing.