![]() |
VOOZH | about |
dotnet add package BoxOfYellow.ConsoleMarkdownRenderer.Fakes --version 0.11.1
NuGet\Install-Package BoxOfYellow.ConsoleMarkdownRenderer.Fakes -Version 0.11.1
<PackageReference Include="BoxOfYellow.ConsoleMarkdownRenderer.Fakes" Version="0.11.1" />
<PackageVersion Include="BoxOfYellow.ConsoleMarkdownRenderer.Fakes" Version="0.11.1" />Directory.Packages.props
<PackageReference Include="BoxOfYellow.ConsoleMarkdownRenderer.Fakes" />Project file
paket add BoxOfYellow.ConsoleMarkdownRenderer.Fakes --version 0.11.1
#r "nuget: BoxOfYellow.ConsoleMarkdownRenderer.Fakes, 0.11.1"
#:package BoxOfYellow.ConsoleMarkdownRenderer.Fakes@0.11.1
#addin nuget:?package=BoxOfYellow.ConsoleMarkdownRenderer.Fakes&version=0.11.1Install as a Cake Addin
#tool nuget:?package=BoxOfYellow.ConsoleMarkdownRenderer.Fakes&version=0.11.1Install as a Cake Tool
Test fakes for ConsoleMarkdownRenderer, enabling dependency injection and unit testing of code that displays markdown.
dotnet add package BoxOfYellow.ConsoleMarkdownRenderer.Fakes
FakeMarkdownDisplayer implements IMarkdownDisplayer and records all calls for assertion:
using BoxOfYellow.ConsoleMarkdownRenderer;
using BoxOfYellow.ConsoleMarkdownRenderer.Fakes;
var fake = new FakeMarkdownDisplayer();
// Pass the fake wherever IMarkdownDisplayer is expected
await fake.DisplayMarkdownAsync(new Uri("https://example.com/readme.md"));
// Assert calls were made as expected
Assert.AreEqual(1, fake.Calls.Count);
Assert.AreEqual("https://example.com/readme.md", fake.Calls[0].Uri?.ToString());
Both overloads (URI-based and text-based) are recorded:
await fake.DisplayMarkdownAsync("# Hello", new Uri("file:///base/"));
Assert.AreEqual("# Hello", fake.Calls[0].Text);
ValidatingFakeMarkdownDisplayerValidatingFakeMarkdownDisplayer implements IMarkdownDisplayer like FakeMarkdownDisplayer, but under the covers each call is delegated to a real MarkdownDisplayer wired up against an isolated Spectre.Console.Testing.TestConsole. That means:
AnsiConsole.Console is restored on exit (even on exception).For each call, the fake captures structured warning data from the renderer:
ConsoleObjectRenderer knows how to render (the IncludeDebug "Unhandled <Name>" warning).allowFollowingLinks: true, which would trigger the "Non-interactive terminal detected. The following links are available but cannot be followed interactively" warning in CI/non-interactive terminals.ConsoleEmphasisInlineRenderer (rendered as e.g. (!1)).using BoxOfYellow.ConsoleMarkdownRenderer;
using BoxOfYellow.ConsoleMarkdownRenderer.Fakes;
var fake = new ValidatingFakeMarkdownDisplayer();
await fake.DisplayMarkdownAsync("# Title\n\nSome **bold** text.", allowFollowingLinks: true);
// Throws MarkdownValidationException if any warning condition is detected.
fake.AssertNoWarnings();
// Or assert each category individually:
fake.AssertNoUnhandledTypes();
fake.AssertNoUnknownEmphasisDelimiters();
fake.AssertNoUnusableLinkWarnings();
You can also inspect the per-call validation results — useful for learning exactly what tripped the unknown-emphasis catch-all:
foreach (var call in fake.Calls)
{
foreach (var d in call.Validation.UnknownEmphasisDelimiters)
Console.WriteLine($"Unknown emphasis delimiter: {d.DelimiterChar} x{d.DelimiterCount}");
foreach (var t in call.Validation.UnhandledTypes)
Console.WriteLine($"Unhandled markdown type: {t.Name}");
foreach (var link in call.Validation.FollowableLinks)
Console.WriteLine($"Followable link: {link.Url}");
}
Set recursive: true in the constructor and the fake will follow every markdown link discovered in each rendered document, validate it the same way, and record a child call. Visited absolute URIs are tracked per top-level call to avoid cycles. Recursion is bounded by maxDepth (default 10) and maxFiles (default 100) guardrails — AssertNoWarnings() will also fail if either was hit, and you can inspect MaxDepthReached/FilesProcessed/ExceededMaxDepth/ExceededMaxFiles directly.
var fake = new ValidatingFakeMarkdownDisplayer(httpClientFactory, recursive: true);
await fake.DisplayMarkdownAsync(new Uri("https://example.com/index.md"), allowFollowingLinks: true);
fake.AssertNoWarnings(); // covers the root document AND every linked .md
foreach (var call in fake.Calls.Where(c => c.IsRecursive))
Console.WriteLine($"Recursively validated (depth {call.Depth}): {call.Uri}");
IHttpClientFactoryBecause each call is delegated to a real MarkdownDisplayer, the URI overload (and recursive link-following) will perform real HTTP requests against any web URI. Hand the fake an IHttpClientFactory to stub those requests out:
var fake = new ValidatingFakeMarkdownDisplayer(myFactory);
A few easy ways to build a factory that maps URIs to canned responses:
RichardSzalay.MockHttp — mockHttp.When("https://example.com/*.md").Respond("text/plain", "# stub");, then wrap the resulting HttpClient in a one-line IHttpClientFactory.
A small hand-rolled DelegatingHandler when you don't want a new dependency:
sealed class StubHttpClientFactory : IHttpClientFactory
{
readonly Func<Uri, HttpResponseMessage> _responder;
public StubHttpClientFactory(Func<Uri, HttpResponseMessage> responder) => _responder = responder;
public HttpClient CreateClient(string name) => new(new Handler(_responder));
sealed class Handler(Func<Uri, HttpResponseMessage> responder) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct)
=> Task.FromResult(responder(req.RequestUri!));
}
}
Moq + a custom HttpMessageHandler if you already use Moq in your test suite.
Thread safety:
ValidatingFakeMarkdownDisplayertemporarily swapsAnsiConsole.Consoleon every call. Like other things that touchAnsiConsole.Console, do not run multiple validations concurrently in the same process.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.