CodeFixTestContext¶
Test Roslyn code fix providers with before/after source assertions.
See also: RoslynTestBase | Analyzer Testing | Workspace
Overview¶
CodeFixTestContext tests code fix providers by applying a fix to source code and verifying the transformed result.
await AnalyzeAndFixWith<MyAnalyzer, MyCodeFix>(source)
.ForDiagnostic("MY001")
.ShouldProduce(expectedSource);
Entry Points¶
From RoslynTestBase:
For Analyzer Diagnostics¶
Use when your code fix targets diagnostics from a custom analyzer:
CodeFixTestContext AnalyzeAndFixWith<TAnalyzer, TCodeFix>(string source)
where TAnalyzer : DiagnosticAnalyzer, new()
where TCodeFix : CodeFixProvider, new()
For Compiler Diagnostics¶
Use when your code fix targets compiler errors/warnings (CS* codes):
Basic Usage¶
Fixing Analyzer Diagnostics¶
[Test]
public async Task AddsPartialModifier()
{
var source = """
[GenerateEquality]
public class Customer { }
""";
var expected = """
[GenerateEquality]
public partial class Customer { }
""";
await AnalyzeAndFixWith<PartialAnalyzer, AddPartialCodeFix>(source)
.ForDiagnostic("GEN001")
.ShouldProduce(expected);
}
Fixing Compiler Diagnostics¶
[Test]
public async Task AddsUsingDirective()
{
var source = """
public class Foo
{
List<int> items;
}
""";
var expected = """
using System.Collections.Generic;
public class Foo
{
List<int> items;
}
""";
await FixWith<AddUsingCodeFix>(source)
.ForDiagnostic("CS0246")
.ShouldProduce(expected);
}
Adding an Analyzer Dynamically¶
You can specify an analyzer after creating the context:
await FixWith<MyCodeFix>(source)
.WithAnalyzer<MyAnalyzer>()
.ForDiagnostic("MY001")
.ShouldProduce(expectedSource);
This is equivalent to AnalyzeAndFixWith<MyAnalyzer, MyCodeFix>.
How Diagnostics Are Found¶
Analyzer Diagnostics¶
When you use AnalyzeAndFixWith or WithAnalyzer:
- The analyzer runs against the source
- First diagnostic matching the ID is selected
- Code fix is invoked for that diagnostic
Compiler Diagnostics¶
When you use FixWith without an analyzer:
- Semantic model is queried for compiler diagnostics
- First diagnostic matching the ID is selected
- Code fix is invoked for that diagnostic
Common Patterns¶
Testing Multiple Code Fixes¶
[Test]
public async Task FixesMultipleIssues()
{
// Test first fix
var source1 = "public class Foo { }";
var expected1 = "public partial class Foo { }";
await AnalyzeAndFixWith<MyAnalyzer, AddPartialFix>(source1)
.ForDiagnostic("MY001")
.ShouldProduce(expected1);
// Test second fix
var source2 = "public class Bar { }";
var expected2 = "public sealed class Bar { }";
await AnalyzeAndFixWith<MyAnalyzer, AddSealedFix>(source2)
.ForDiagnostic("MY002")
.ShouldProduce(expected2);
}
Testing Fix Does Nothing for Wrong Diagnostic¶
[Test]
public async Task DoesNotFixUnrelatedDiagnostic()
{
var source = """
public class Foo { }
""";
var context = FixWith<AddPartialFix>(source);
// This should fail because CS0246 isn't what AddPartialFix handles
// You'd typically test this by checking no code action is registered
}
Preserving Formatting¶
The assertion normalizes line endings for comparison, so you don't need to worry about \r\n vs \n:
var expected = """
public partial class Foo
{
public string Name { get; set; }
}
""";
// Works regardless of platform line endings
await AnalyzeAndFixWith<MyAnalyzer, MyFix>(source)
.ForDiagnostic("MY001")
.ShouldProduce(expected);
Limitations¶
First Code Action Only¶
If your code fix registers multiple code actions, only the first one is applied and verified.
First Diagnostic Only¶
If multiple diagnostics match the ID, only the first one triggers the fix.
No Batch Testing¶
Currently tests one fix at a time. For "fix all" scenarios, you'd need to apply fixes iteratively.
How It Works¶
- Source is compiled into a
Documentin anAdhocWorkspace - Analyzer runs (if provided) to produce diagnostics
CodeFixProvider.RegisterCodeFixesAsyncis called for the target diagnostic- First registered
CodeActionis executed ApplyChangesOperationproduces the new solution- Fixed document text is compared to expected source
License¶
RPL-1.5 (Reciprocal Public License) — Real reciprocity, no loopholes.
You can use this code, modify it, and share it freely. But when you deploy it — internally or externally, as a service or within your company — you share your improvements back under the same license.
Why? We believe if you benefit from this code, the community should benefit from your improvements. That's the deal we think is fair.
Personal research and experimentation? No obligations. Go learn, explore, and build.
See LICENSE for the full legal text.