NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Sunday, 25 August 2024
//Less than a minute
In diesem Beitrag werde ich mit dem Hinzufügen von Unit Testing für diese Website beginnen. Dies wird nicht ein vollständiges Tutorial zu Unit Testing, sondern eine Reihe von Beiträgen, wie ich hinzufügen Unit Testing zu dieser Website. In diesem Beitrag teste ich einige Dienste durch Spott DbContext; dies ist, um jede DB spezifische Shennanigans zu vermeiden.
Unit Testing ist eine Möglichkeit, einzelne Komponenten Ihres Codes isoliert zu testen. Dies ist aus mehreren Gründen nützlich:
Es gibt eine Reihe von anderen Arten von Tests, die Sie tun können. Hier sind ein paar:
Ich werde xUnit für meine Tests verwenden. Dies wird standardmäßig in ASP.NET Core Projekten verwendet. Ich werde auch Moq benutzen, um den DbContext zusammen mit
In Vorbereitung darauf habe ich ein Interface für meinen DbContext hinzugefügt. Das ist so, dass ich den DbContext in meinen Tests verspotten kann. Hier ist die Schnittstelle:
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);
}
Es ist ziemlich einfach, nur unsere DBSets und die SaveChangesAsync-Methode zu entlarven.
Wir fügen dann eine neue Klasse zu unserem Mostlylucid.Test
Projekt mit einer Erweiterungsmethode, um unsere Abfrage einzurichten:
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);
}
}
Sie werden sehen, dass dies die MockQueryable.Moq
Erweiterungsmethode, um den Mock zu erstellen. Was dann unsere IQueryable Objekte und IAsyncQueryable Objekte aufstellt.
Ein Kerngrundsatz von Unit Testing ist, dass jeder Test eine 'Einheit' der Arbeit sein sollte und nicht vom Ergebnis eines anderen Tests abhängig ist (aus diesem Grund verspotten wir unseren DbContext).
In unserem neuen BlogServiceFetchTests
Klasse haben wir unseren Testkontext im Konstruktor eingerichtet:
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();
}
Ich habe das ziemlich heftig kommentiert, damit du sehen kannst, was los ist. Wir bauen eine ServiceCollection
Das ist eine Sammlung von Dienstleistungen, die wir in unseren Tests nutzen können. Dann erschaffen wir einen Spott über unsere IMostlylucidDBContext
und registrieren Sie es in der ServiceCollection
......................................................................................................... Wir registrieren dann alle anderen Dienstleistungen, die wir für unsere Tests benötigen. Schließlich bauen wir die ServiceProvider
von denen wir unsere Dienste beziehen können.
Ich begann mit dem Hinzufügen einer einzigen Testklasse, die oben genannten BlogServiceFetchTests
Unterricht. Dies ist ein Testkurs für die Post immer Methoden meiner EFBlogService
Unterricht.
Jeder Test verwendet eine gemeinsame SetupBlogService
Methode, um eine neue Bevölkerung zu erhalten EFBlogService
Gegenstand. Damit wir den Dienst isoliert testen können.
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>();
}
Dies ist eine einfache Erweiterungsklasse, die uns eine Reihe von pupulierten BlogPostEntity
Gegenstand. Damit wir unseren Service mit einer Reihe verschiedener Objekte testen können.
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;
}
Sie können sehen, dass alles, was dies tut, eine bestimmte Anzahl von Blog-Beiträgen mit Sprachen und Kategorien zurückgibt. Wir fügen jedoch immer ein 'root' Objekt hinzu, das es uns erlaubt, uns in unseren Tests auf ein bekanntes Objekt verlassen zu können.
Jeder Test ist so konzipiert, dass er einen Aspekt der Ergebnisse der Beiträge prüft.
In den beiden untenstehenden Beispielen testen wir einfach, dass wir alle Beiträge bekommen können und dass wir Beiträge nach Sprache bekommen können.
[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());
}
Ein wichtiges Konzept in Unit-Testing ist 'Testfehler', wo Sie feststellen, dass Ihr Code in der Art und Weise, wie Sie es erwarten, fehlschlägt.
In den Tests unten testen wir zunächst, dass unser Paging-Code wie erwartet funktioniert. Wir testen dann, dass wir, wenn wir nach mehr Seiten fragen, als wir haben, ein leeres Ergebnis (und kein Fehler) erhalten.
[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);
}
Dies ist ein einfacher Start für unsere Unit Testing. Im nächsten Beitrag werden wir Tests für weitere Dienste und Endpunkte hinzufügen. Wir werden auch untersuchen, wie wir unsere Endpunkte mit Integrationstests testen können.