ZCrew.Extensions.CodeAnalysis.CSharp.Testing
1.0.0-preview.6
See the version list below for details.
dotnet add package ZCrew.Extensions.CodeAnalysis.CSharp.Testing --version 1.0.0-preview.6
NuGet\Install-Package ZCrew.Extensions.CodeAnalysis.CSharp.Testing -Version 1.0.0-preview.6
<PackageReference Include="ZCrew.Extensions.CodeAnalysis.CSharp.Testing" Version="1.0.0-preview.6" />
<PackageVersion Include="ZCrew.Extensions.CodeAnalysis.CSharp.Testing" Version="1.0.0-preview.6" />
<PackageReference Include="ZCrew.Extensions.CodeAnalysis.CSharp.Testing" />
paket add ZCrew.Extensions.CodeAnalysis.CSharp.Testing --version 1.0.0-preview.6
#r "nuget: ZCrew.Extensions.CodeAnalysis.CSharp.Testing, 1.0.0-preview.6"
#:package ZCrew.Extensions.CodeAnalysis.CSharp.Testing@1.0.0-preview.6
#addin nuget:?package=ZCrew.Extensions.CodeAnalysis.CSharp.Testing&version=1.0.0-preview.6&prerelease
#tool nuget:?package=ZCrew.Extensions.CodeAnalysis.CSharp.Testing&version=1.0.0-preview.6&prerelease
ZCrew.Extensions.CodeAnalysis.CSharp.Testing
Testing library for verifying Roslyn source generators. It drives a generator through Roslyn's
CSharpSourceGeneratorTest harness from a small JSON descriptor, so each test case is just a set of input and
expected-output files plus a .json that ties them together.
Installation
Available on NuGet for .NET Standard 2.0:
<PackageReference Include="ZCrew.Extensions.CodeAnalysis.CSharp.Testing" />
Writing a test case
A test case is three kinds of file in a TestCases/ folder next to your test class:
- Input sources (e.g.
MyCase.Attribute.cs) compiled as input to the generator. - Expected generated files (e.g.
MyCase.SourceText.g.cs) the output to verify against. - A JSON descriptor (
MyCase.json) maps the inputs, expected outputs, and any expected diagnostics:
{
"SourceFiles": [
{ "SourceFileName": "MyCase.Attribute.cs" }
],
"GeneratedFiles": [
{
"SourceFileName": "MyCase.SourceText.g.cs",
"GeneratedFileName": "MyNamespace.MyTypeSourceText.g.cs"
}
]
}
SourceFileName is the file on disk; GeneratedFileName is the hint name your generator passes to
context.AddSource(hintName, ...).
Expecting diagnostics
Declare expected diagnostics where they occur:
- On a source file or generated file entry, an
ExpectedDiagnosticsarray asserts diagnostics located in that file. Give each a location bySnippet(the start of its single occurrence in that file) or by an explicit 1-basedLine/Column. - At the top level, an
ExpectedDiagnosticsarray asserts diagnostics with no location (Location.None) — for exampleCS5001or a compilation-level analyzer diagnostic.
Each entry has an Id, an optional Severity (defaults to Error), and an optional Message to match exactly.
{
"SourceFiles": [
{
"SourceFileName": "MyCase.Attribute.cs",
"ExpectedDiagnostics": [
{ "Id": "CS0246", "Snippet": "CreateService<T>(" },
{ "Id": "CS0246", "Line": 10, "Column": 17 }
]
}
],
"GeneratedFiles": [
{
"SourceFileName": "MyCase.SourceText.g.cs",
"GeneratedFileName": "MyNamespace.MyTypeSourceText.g.cs",
"ExpectedDiagnostics": [
{ "Id": "CS0219", "Severity": "Warning", "Snippet": "unused" }
]
}
],
"ExpectedDiagnostics": [
{ "Id": "CS5001" }
]
}
Only the diagnostic's start is asserted, so the reported span may extend past the located snippet. A snippet
that is missing or appears more than once in its file fails the test with a helpful message — use a more specific
snippet or an explicit Line/Column.
Wiring the test
Configure a shared baseline once, then load and run each case. Resolve the TestCases folder from the source
tree with TestPath.ForCaller() so fixtures are checked into git:
private static readonly SourceGeneratorTestBuilder<MyGenerator, DefaultVerifier> Baseline =
SourceGeneratorTestBuilder<MyGenerator>
.CreateDefaultBuilder()
.WithReferenceAssemblies(ReferenceAssemblies.Net.Net100)
.WithGeneratorPostInitializationSources();
private static readonly TestPath testCases = TestPath.ForCaller() / "TestCases";
[Theory]
[InlineData("MyCase.json")]
public async Task Generates_expected_sources(string descriptor)
{
var testCase = await JsonTestCase.FromJsonFileAsync(testCases / descriptor);
var test = await Baseline.BuildAsync(testCase);
await test.RunAsync();
}
The builder is immutable (every With* call forks a new builder) so a fully configured baseline can be shared
as a fixture and specialized per test without affecting other tests.
Project setup
Keep fixture files out of the compilation and out of the build output. Because the test resolves them from the
source tree via TestPath.ForCaller(), they do not need to be copied to bin:
<ItemGroup>
<Compile Remove="**/TestCases/**/*.cs" />
<None Include="**/TestCases/**/*.json" Exclude="bin/**/*.json" />
<None Include="**/TestCases/**/*.cs" Exclude="bin/**/*.cs" />
</ItemGroup>
Updating expected files in place
Hand-writing and maintaining .g.cs files is tedious and I hate it, you should too. Add WithExpectedSourceUpdates()
to the baseline and, whenever the generator's output differs from (or is missing) an expected file, the test
overwrites it on disk with the produced output and still fails:
.WithExpectedSourceUpdates(enabled: Environment.GetEnvironmentVariable("CI") is null)
Workflow:
- Change your generator, or add a new test case whose
.g.csfiles do not exist yet. - Run the tests. Mismatched and missing expected files are rewritten in place and the run is red.
- Review the changes in source control
- Keep them (re-run → green) or revert them.
The tests compare the output to previous file contents, so this won't cause false-positives. This does mean that when you get a failure and you've fixed it: the next test will fail (the previous file contents were broken and the new ones are fixed), and so running the tests again will then pass.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing (>= 1.1.4)
- Microsoft.CodeAnalysis.CSharp.Workspaces (>= 5.3.0)
- System.Text.Json (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview.8 | 87 | 8/1/2026 |
| 1.0.0-preview.6 | 75 | 7/5/2026 |
| 1.0.0-preview.5 | 70 | 7/5/2026 |
| 1.0.0-preview.4 | 65 | 6/16/2026 |
| 1.0.0-preview.3 | 64 | 6/12/2026 |