ANcpLua.Roslyn.Utilities.Testing provides fluent base classes for testing Roslyn components:
| Base Class | Purpose |
|---|
AnalyzerTest<TAnalyzer> | Test diagnostic analyzers |
CodeFixTest<TAnalyzer, TCodeFix> | Test code fix providers |
RefactoringTest<TRefactoring> | Test code refactoring providers |
Test<TGenerator> | Test incremental source generators |
These base classes work with xUnit v3’s ambient TestContext. Use
TestContext.Current.TestOutputHelper for diagnostic output without
constructor injection.
Installation
<PackageReference Include="ANcpLua.Roslyn.Utilities.Testing" Version="1.15.0" />
AnalyzerTest
Base class for testing diagnostic analyzers with pre-configured .NET 10 reference assemblies.
Basic Usage
using ANcpLua.Roslyn.Utilities.Testing;
public class MyAnalyzerTests : AnalyzerTest<MyAnalyzer>
{
[Fact]
public Task ShouldReportWarning() => VerifyAsync("""
class C
{
void M() { {|MY001:BadCode()|}; }
}
""");
[Fact]
public Task ShouldNotReport() => VerifyAsync("""
class C
{
void M() { GoodCode(); }
}
""");
}
Use diagnostic markup {|DIAGNOSTIC_ID:code|} to mark expected diagnostic locations.
Testing with Additional Files
For analyzers that inspect non-C# files (MSBuild props, JSON configs, etc.):
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
public class PropsAnalyzerTests : AnalyzerTest<MyPropsAnalyzer>
{
[Fact]
public Task ShouldWarnWhenMissingImport()
{
const string directoryBuildProps = """
<Project>
<PropertyGroup>
<SomeProperty>Value</SomeProperty>
</PropertyGroup>
</Project>
""";
var expected = new DiagnosticResult("MY001", DiagnosticSeverity.Warning)
.WithLocation("Directory.Build.props", 1, 1);
return VerifyAsync(
source: "public class C { }",
additionalFiles: [("Directory.Build.props", directoryBuildProps)],
expectedDiagnostics: [expected]);
}
[Fact]
public Task ShouldNotWarnWhenImportPresent()
{
const string directoryBuildProps = """
<Project>
<Import Project="Version.props" />
</Project>
""";
// No expected diagnostics = verify clean compilation
return VerifyAsync(
source: "public class C { }",
additionalFiles: [("Directory.Build.props", directoryBuildProps)]);
}
}
Target Framework Selection
// Default: .NET 10 references
await VerifyAsync(source);
// For netstandard2.0 analyzers (e.g., source generators)
await VerifyAsync(source, useNet10References: false);
CodeFixTest
Base class for testing code fix providers that transform diagnostic-marked code.
using ANcpLua.Roslyn.Utilities.Testing;
public class MyCodeFixTests : CodeFixTest<MyAnalyzer, MyCodeFix>
{
[Fact]
public Task ShouldFixWarning() => VerifyAsync(
source: """
class C
{
void M() { {|MY001:BadCode()|}; }
}
""",
fixedSource: """
class C
{
void M() { GoodCode(); }
}
""");
}
The test framework automatically runs the analyzer, triggers the code fix, and
verifies the result matches fixedSource.
RefactoringTest
Base class for testing code refactoring providers (lightbulb suggestions without diagnostics).
Basic Usage
using ANcpLua.Roslyn.Utilities.Testing;
public class MakeStaticRefactoringTests : RefactoringTest<MakeStaticLambdaRefactoring>
{
[Fact]
public Task ShouldMakeLambdaStatic() => VerifyAsync(
source: """
using System;
class C
{
Func<int, int> f = [|x => x * 2|];
}
""",
fixedSource: """
using System;
class C
{
Func<int, int> f = static x => x * 2;
}
""");
}
Use span markup [|code|] to mark the refactoring trigger location.
Verifying No Refactoring Offered
[Fact]
public Task ShouldNotOfferWhenAlreadyStatic() => VerifyNoRefactoringAsync("""
using System;
class C
{
Func<int, int> f = [|static x => x * 2|];
}
""");
[Fact]
public Task ShouldNotOfferWhenCapturing() => VerifyNoRefactoringAsync("""
using System;
class C
{
void M()
{
int captured = 5;
Func<int, int> f = [|x => x + captured|];
}
}
""");
GeneratorTest
Fluent API for testing Roslyn incremental source generators with built-in caching validation.
Basic Usage
using ANcpLua.Roslyn.Utilities.Testing;
public class MyGeneratorTests
{
[Fact]
public async Task ShouldGenerateExpectedOutput()
{
const string source = """
[GenerateDto]
public partial class User
{
public string Name { get; set; }
}
""";
using var result = await Test<MyGenerator>.Run(source);
result
.Produces("User.g.cs", """
public partial class User
{
public UserDto ToDto() => new(Name);
}
""")
.IsCached()
.IsClean();
}
}
The Test<TGenerator> static class automatically runs your generator twice to validate incremental caching
behavior.
Custom Configuration
For complex scenarios, use the configuration overload:
[Fact]
public async Task ShouldHandleAdditionalText()
{
using var result = await Test < MyGenerator >.Run(engine => engine
.WithSource("""
public partial class Config {
}
""")
.WithAdditionalText("config.json", """{"setting": "value"}""")
.WithLanguageVersion(LanguageVersion.CSharp12)
.WithStepTracking(true))
result.Produces("Config.g.cs").IsCached();
}
GeneratorTestEngine Methods
| Method | Description |
|---|
WithSource(string) | Add C# source code to the compilation |
WithAdditionalText(path, text) | Add an additional file (JSON, XML, etc.) |
WithReference(MetadataReference) | Add a metadata reference |
WithLanguageVersion(version) | Set C# language version (default: latest) |
WithReferenceAssemblies(assemblies) | Set reference assemblies package |
WithAnalyzerConfigOptions(provider) | Set MSBuild property access |
WithStepTracking(bool) | Enable/disable step tracking for caching analysis |
GeneratorResult Assertions
The GeneratorResult class provides a fluent API for verifying generator output.
Output Verification
result
.Produces("Output.g.cs") // File exists
.Produces("Output.g.cs", expectedContent) // Exact content match
.Produces("Output.g.cs", "partial class", exactMatch: false); // Contains
Compilation Verification
result
.IsClean() // No diagnostics of any severity
.Compiles(); // No errors (warnings allowed)
Diagnostic Assertions
result
.HasDiagnostic("GEN001") // Diagnostic exists
.HasDiagnostic("GEN001", DiagnosticSeverity.Warning) // With severity
.HasNoDiagnostic("GEN002"); // Diagnostic absent
Custom File Assertions
result.File("Output.g.cs", content =>
{
Assert.Contains("public partial class", content);
Assert.DoesNotContain("private", content);
});
GeneratorResult implements IDisposable and calls Verify() on dispose.
Use using var result = ... to automatically throw on assertion failures.
Caching Validation
Incremental generators must cache their outputs properly to avoid recomputation during IDE typing scenarios. The testing framework validates caching behavior automatically.
Basic Caching Check
result.IsCached(); // All pipeline steps must be cached
Checking Specific Steps
result.IsCached("TransformStep", "CombineStep"); // Only check named steps
Understanding the Caching Report
var report = result.CachingReport;
Console.WriteLine($"Generator: {report.GeneratorName}");
Console.WriteLine($"Produced output: {report.ProducedOutput}");
foreach (var step in report.ObservableSteps)
{
Console.WriteLine($"Step: {step.StepName}")
Console.WriteLine($" Cached: {step.Cached}")
Console.WriteLine($" Unchanged: {step.Unchanged}")
Console.WriteLine($" Modified: {step.Modified}")
Console.WriteLine($" New: {step.New}")
Console.WriteLine($" Removed: {step.Removed}")
Console.WriteLine($" Success: {step.IsCachedSuccessfully}")
}
Forbidden Types
Certain Roslyn types must never be cached because they prevent proper incremental caching:
| Forbidden Type | Reason |
|---|
ISymbol | Not value-equal across compilations |
Compilation | Contains mutable state |
SemanticModel | Tied to specific compilation |
SyntaxNode | Reference identity, not structural equality |
SyntaxTree | Reference identity, not structural equality |
IOperation | Compilation-specific |
// Validates no forbidden types in any step
result.HasNoForbiddenTypes();
// IsCached() automatically checks for forbidden types
result.IsCached(); // Fails if ISymbol, Compilation, etc. are cached
Caching forbidden types causes memory leaks and IDE performance degradation.
Extract primitive data (strings, booleans, etc.) from Roslyn types before
caching.
Step Tracking
Enable step tracking to analyze which pipeline steps were executed and their caching states.
How Step Tracking Works
- The test engine runs your generator twice with identical input
- The first run populates the cache
- The second run reveals caching behavior
Step States
| State | Description |
|---|
Cached | Output reused from previous run (optimal) |
Unchanged | Recomputed but produced same value (equality worked) |
Modified | Value changed between runs (cache miss) |
New | Additional item produced |
Removed | Item no longer produced |
A successfully cached step produces only Cached or Unchanged outputs.
Named Step Tracking
Use WithTrackingName in your generator to enable fine-grained analysis:
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider
.CreateSyntaxProvider(...)
.WithTrackingName("ParseStep"); // Named for testing
var combined = provider
.Combine(context.CompilationProvider)
.WithTrackingName("CombineStep");
context.RegisterSourceOutput(combined, ...);
}
Then verify specific steps:
result.IsCached("ParseStep", "CombineStep");
Method Reference
AnalyzerTest<TAnalyzer>
| Method | Description |
|---|
VerifyAsync(source) | Verify analyzer produces expected diagnostics marked in source |
VerifyAsync(source, additionalFiles, expectedDiagnostics?) | Verify with additional files and explicit diagnostics |
CodeFixTest<TAnalyzer, TCodeFix>
| Method | Description |
|---|
VerifyAsync(source, fixedSource) | Verify code fix transforms source to expected output |
RefactoringTest<TRefactoring>
| Method | Description |
|---|
VerifyAsync(source, fixedSource) | Verify refactoring transforms source to expected output |
VerifyNoRefactoringAsync(source) | Verify no refactoring is offered at the marked span |
Test<TGenerator>
| Method | Description |
|---|
Run(source) | Run generator with source and return result |
Run(configure) | Run generator with custom configuration |
GeneratorResult
| Method | Description |
|---|
Produces(hintName) | Assert file exists |
Produces(hintName, content) | Assert file exists with exact content |
Produces(hintName, content, false) | Assert file contains content |
IsClean() | Assert no diagnostics |
Compiles() | Assert no errors (warnings allowed) |
IsCached() | Assert all steps are cached |
IsCached(stepNames) | Assert specific steps are cached |
HasDiagnostic(id) | Assert diagnostic exists |
HasDiagnostic(id, severity) | Assert diagnostic exists with severity |
HasNoDiagnostic(id) | Assert diagnostic does not exist |
HasNoForbiddenTypes() | Assert no ISymbol/Compilation/etc. cached |
File(hintName, action) | Execute custom assertion on file content |
GeneratorCachingReport
| Property | Description |
|---|
GeneratorName | Name of the generator being tested |
ProducedOutput | Whether generator produced any files |
ObservableSteps | List of user-defined pipeline steps |
ForbiddenTypeViolations | List of cached forbidden types detected |
GeneratorStepAnalysis
| Property | Description |
|---|
StepName | Name of the pipeline step |
Cached | Count of cached outputs |
Unchanged | Count of unchanged outputs |
Modified | Count of modified outputs |
New | Count of new outputs |
Removed | Count of removed outputs |
HasForbiddenTypes | Whether step has forbidden type violations |
IsCachedSuccessfully | True if no Modified/New/Removed outputs |
Test Project Patterns
Minimal Generator Test Project
A clean generator test project requires only essential packages:
<Project Sdk="ANcpLua.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3.mtp-v2"/>
<PackageReference Include="AwesomeAssertions"/>
<PackageReference Include="Verify.XunitV3"/>
<PackageReference Include="Verify.SourceGenerators"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
<PackageReference Include="ANcpLua.Roslyn.Utilities" GeneratePathProperty="true" PrivateAssets="all"/>
<PackageReference Include="ANcpLua.Roslyn.Utilities.Testing"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../MyGenerator/MyGenerator.csproj" OutputItemType="Analyzer"/>
</ItemGroup>
<ItemGroup>
<Analyzer Include="$(PkgANcpLua_Roslyn_Utilities)/lib/netstandard2.0/ANcpLua.Roslyn.Utilities.dll"/>
</ItemGroup>
</Project>
The Analyzer item loads ANcpLua.Roslyn.Utilities as an analyzer dependency
alongside your generator.
Shared Test Base Class
Create a base class to centralize test infrastructure:
using ANcpLua.Roslyn.Utilities.Testing;
using Microsoft.AspNetCore.Http;
namespace MyGenerator.Tests;
public abstract class GeneratorTestBase
{
protected static readonly Type[] RequiredTypes =
[
typeof (HttpContext),
// Add types your generator needs
];
protected static async Task < GeneratorResult > RunAsync(string source)
{
using
var scope = TestConfiguration.WithAdditionalReferences(RequiredTypes);
return await Test < MyGenerator >
.
Run(source, TestContext.Current.CancellationToken);
}
protected static async Task VerifyAsync(string source)
{
using
var result = await RunAsync(source);
await Verify(new
{
GeneratedSources = result.Files
.Select(static f
=>
new {f.HintName, Source = f.Content}
)
.
OrderBy(static
s => s.HintName
),
Diagnostics = result.Diagnostics
.Select(static
d => new {d.Id, Severity = d.Severity.ToString(), Message = d.GetMessage()}
)
.
OrderBy(static
d => d.Id
)
}).
UseDirectory("Snapshots");
}
}
Verify Module Initializer
Configure Verify for Roslyn types:
using System.Runtime.CompilerServices;
namespace MyGenerator.Tests;
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Initialize()
{
VerifySourceGenerators.Initialize();
VerifierSettings.AddExtraSettings(static
settings =>
settings.Converters.Add(new EquatableArrayJsonConverter())
)
}
}
file sealed class EquatableArrayJsonConverter : WriteOnlyJsonConverter
{
public override void Write(VerifyJsonWriter writer, object value)
{
var type = value.GetType();
if (!type.IsGenericType || type.GetGenericTypeDefinition().Name != "EquatableArray`1") {
writer.Serialize(value);
return;
}
var itemsProperty = type.GetProperty("Items");
var items = itemsProperty?.GetValue(value);
if (items is
null
)
{
writer.WriteStartArray();
writer.WriteEndArray();
return;
}
var toArrayMethod = items.GetType().GetMethod("ToArray");
writer.Serialize(toArrayMethod?.Invoke(items, null) ?? Array.Empty < object > ());
}
public override bool CanConvert(Type type) =>
type.IsGenericType && type.GetGenericTypeDefinition().Name == "EquatableArray`1";
}
Test File Organization
tests/MyGenerator.Tests/
├── ModuleInitializer.cs # Verify configuration
├── GeneratorTestBase.cs # Shared infrastructure
├── GeneratorOutputTests.cs # Output verification
├── AnalyzerDiagnosticTests.cs
└── Snapshots/ # Verify snapshot files
└── *.verified.txt
Keep tests focused: one test class per concern. Avoid duplicate test
infrastructure across files.
Dynamic Compilation
The Compile class provides a fluent API for dynamic compilation in tests, useful for testing code that needs to compile and execute user-provided source code.
Basic Usage
using ANcpLua.Roslyn.Utilities.Testing;
// Compile and assert success
Compile.Source("""
public class Calculator
{
public int Add(int a, int b) => a + b;
}
""")
.WithCommonReferences()
.Build()
.ShouldSucceed();
// Get assembly directly (throws on failure)
var assembly = Compile.Source(code).BuildOrThrow();
// Create instance from compiled code
var calc = Compile.Source(code)
.Build()
.ShouldSucceed()
.CreateInstance<ICalculator>("Calculator");
Compile Builder Methods
| Method | Description |
|---|
Source(code) | Create compiler with source code |
Empty() | Create empty compiler for configuration |
WithSource(code) | Add additional source code |
WithSources(codes) | Add multiple source files |
WithReference<T>() | Add reference from type’s assembly |
WithReference(assembly) | Add reference from assembly |
WithReference(path) | Add reference from file path |
WithReferences(types) | Add references from multiple types |
WithCommonReferences() | Add Console, Linq, Collections references |
WithAssemblyName(name) | Set output assembly name |
WithOutputKind(kind) | Set output kind |
AsExecutable() | Configure as console application |
WithLanguageVersion(v) | Set C# language version |
WithOptimization() | Enable release mode |
WithUnsafe() | Allow unsafe code |
Build() | Build and return CompileResult |
BuildOrThrow() | Build and return assembly (throws on fail) |
CompileResult Properties
| Property | Description |
|---|
Succeeded | Whether compilation succeeded |
Failed | Whether compilation failed |
Assembly | Loaded assembly if succeeded |
Compilation | CSharpCompilation for inspection |
Diagnostics | All diagnostics |
Errors | Error diagnostics only |
Warnings | Warning diagnostics only |
CompileResult Query Methods
| Method | Description |
|---|
HasError(errorId) | Check if error with ID exists |
HasWarning(warningId) | Check if warning with ID exists |
ContainsType(typeName) | Check if type exists in assembly |
GetType(name) | Get type from assembly |
GetRequiredType(name) | Get type or throw |
CreateInstance(typeName) | Create instance of type |
CreateInstance<T>(typeName) | Create instance cast to T |
CreateRequiredInstance<T>(t) | Create instance or throw |
FormatDiagnostics() | Format diagnostics for display |
GetSourceText() | Get combined source text |
GetSemanticModel() | Get semantic model for 1st tree |
CompileResult Assertions
result
.ShouldSucceed() // Assert compilation succeeded
.ShouldHaveNoWarnings() // Assert no warnings
.ShouldContainType("MyNamespace.MyClass");
result.ShouldFail(); // Assert compilation failed
result.ShouldHaveError("CS0246");
result.ShouldHaveWarning("CS8618");
result.ShouldHaveErrors(3); // At least 3 errors
Log Testing
The LogAssert class provides fluent assertions for FakeLogCollector from Microsoft.Extensions.Logging.Testing.
Basic Usage
using ANcpLua.Roslyn.Utilities.Testing;
using Microsoft.Extensions.Logging.Testing;
var collector = new FakeLogCollector();
var logger = new FakeLogger(collector);
// ... code that logs ...
collector
.ShouldHaveCount(3)
.ShouldContain("started")
.ShouldHaveNoErrors();
Count Assertions
| Method | Description |
|---|
ShouldHaveCount(n) | At least N entries |
ShouldHaveExactCount(n) | Exactly N entries |
ShouldBeEmpty() | No entries |
Content Assertions
| Method | Description |
|---|
ShouldContain(text, comparison?) | Contains text |
ShouldNotContain(text) | Does not contain text |
ShouldMatch(pattern) | Matches regex pattern |
Level Assertions
| Method | Description |
|---|
ShouldHaveLevel(level) | Has log at level |
ShouldNotHaveLevel(l) | No log at level |
ShouldHaveNoErrors() | No Error level logs |
ShouldHaveNoWarnings() | No Warning level logs |
ShouldBeClean() | No errors or warnings |
Combined Assertions
collector.ShouldHave(LogLevel.Error, "connection failed");
Predicate Assertions
collector.ShouldHaveAny(r => r.Exception is not null, "Expected exception log");
collector.ShouldHaveAll(r => r.Level >= LogLevel.Information);
collector.ShouldHaveNone(r => r.Message?.Contains("secret") == true);
Async Waiting
For testing async logging scenarios, use polling assertions:
// Wait for log containing text (5s default timeout)
await collector.ShouldEventuallyContain("operation completed");
// Wait for count
await collector.ShouldEventuallyHaveCount(5);
// Wait for log level
await collector.ShouldEventuallyHaveLevel(LogLevel.Error);
// Custom condition
await collector.ShouldEventuallySatisfy(
logs => logs.Count(r => r.Level == LogLevel.Warning) >= 2,
because: "Expected at least 2 warnings",
timeout: TimeSpan.FromSeconds(10));
Async assertions poll every 25ms and timeout after 5 seconds by default. Pass
a custom timeout parameter for longer operations.
Web Testing
ASP.NET Core integration test base classes with fake logging support.
IntegrationTestBase
Fast integration test base using in-memory TestServer for API tests that don’t require real network I/O.
using ANcpLua.Roslyn.Utilities.Testing.WebTesting;
public class MyApiTests : IntegrationTestBase<Program>
{
public MyApiTests(WebApplicationFactory<Program> factory) : base(factory) { }
[Fact]
public async Task GetEndpoint_ReturnsOk()
{
var response = await Client.GetAsync("/api/items");
response.EnsureSuccessStatusCode();
// Assert on fake logs
Logs.ShouldContain("Request started");
}
protected override void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureTestServices(services); // Adds fake logging
services.AddSingleton<IMyService, FakeMyService>();
}
}
KestrelTestBase
Real Kestrel test base for HTTP/2, WebSockets, SSE, and Playwright testing with real network I/O.
using ANcpLua.Roslyn.Utilities.Testing.WebTesting;
public class WebSocketTests : KestrelTestBase<Program>
{
public WebSocketTests(WebApplicationFactory<Program> factory) : base(factory) { }
[Fact]
public async Task WebSocket_Connects()
{
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri(BaseAddress, "/ws"), CancellationToken.None);
Assert.Equal(WebSocketState.Open, ws.State);
}
}
| Property | Description |
|---|
Client | HttpClient configured for the test server |
Logs | FakeLogCollector for asserting log output |
Factory | Configured WebApplicationFactory |
BaseAddress | Server base address (KestrelTestBase only) |
Analyzer Infrastructure
Base classes for building Roslyn analyzers and code fixes with common patterns.
DiagnosticAnalyzerBase
Base class for diagnostic analyzers with concurrent execution and registration helpers.
using ANcpLua.Roslyn.Utilities.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MyAnalyzer : DiagnosticAnalyzerBase
{
public static readonly DiagnosticDescriptor Rule = new(
"MY001", "Title", "Message", "Category",
DiagnosticSeverity.Warning, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
protected override void RegisterAnalysis(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
}
private void AnalyzeSymbol(SymbolAnalysisContext context) { /* ... */ }
}
CodeFixProviderBase
Base class for code fix providers with batch fixing and equivalence key support.
using ANcpLua.Roslyn.Utilities.CodeFixes;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class MyCodeFix : CodeFixProviderBase<SyntaxNode>
{
public override ImmutableArray<string> FixableDiagnosticIds => ["MY001"];
protected override string Title => "Fix the issue";
protected override string EquivalenceKey => "MyCodeFix";
protected override async Task<SyntaxNode?> GetTargetNodeAsync(
Document document, TextSpan span, CancellationToken ct)
{
var root = await document.GetSyntaxRootAsync(ct);
return root?.FindNode(span);
}
protected override async Task<Document> FixAsync(
Document document, SyntaxNode node, CancellationToken ct)
{
var root = await document.GetSyntaxRootAsync(ct);
var newNode = node.WithLeadingTrivia(/* ... */);
return document.WithSyntaxRoot(root!.ReplaceNode(node, newNode));
}
}
SyntaxModifierExtensions
Helpers for syntax transformations in code fixes.
// Add modifier
var newDecl = methodDecl.AddModifier(SyntaxKind.AsyncKeyword);
// Remove modifier
var newDecl = methodDecl.RemoveModifier(SyntaxKind.PublicKeyword);
// Replace modifier
var newDecl = methodDecl.ReplaceModifier(
SyntaxKind.PrivateKeyword,
SyntaxKind.PublicKeyword);
Integration Testing
For testing that requires real MSBuild execution (SDK features, package behavior, build-time code generation), see MSBuild Testing.
Handling Type Ambiguity
If you encounter ambiguous reference errors between Microsoft.CodeAnalysis.Testing.AnalyzerTest and ANcpLua.Roslyn.Utilities.Testing.AnalyzerTest, use a type alias:
using AnalyzerTestBase = ANcpLua.Roslyn.Utilities.Testing.AnalyzerTest<MyAnalyzer>;
public class MyTests : AnalyzerTestBase
{
// ...
}
```
````