Dena.CodeAnalysis.Testing 1.0.0

Test helpers for DiagnosticAnalyzers

Install-Package Dena.CodeAnalysis.Testing -Version 1.0.0
dotnet add package Dena.CodeAnalysis.Testing --version 1.0.0
<PackageReference Include="Dena.CodeAnalysis.Testing" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Dena.CodeAnalysis.Testing --version 1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Dena.CodeAnalysis.Testing, 1.0.0"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install Dena.CodeAnalysis.Testing as a Cake Addin
#addin nuget:?package=Dena.CodeAnalysis.Testing&version=1.0.0

// Install Dena.CodeAnalysis.Testing as a Cake Tool
#tool nuget:?package=Dena.CodeAnalysis.Testing&version=1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Dena.CodeAnalysis.Testing

NuGet version
CircleCI

This library provides TDD friendly DiagnosticAnalyzer test helpers:

  • DiagnosticAnalyzerRunner

    A runner for Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer.
    The purpose of the runner is providing another runner instead of Microsoft.CodeAnalysis.Analyzer.Testing.AnalyzerVerifier.VerifyAnalyzerAsync.

    Because of the AnalyzerVerifier has several problems:

    1. Using AnalyzerVerifier, it is hard to instantiate analyzer with custom arguments (the custom args may be needed if your analyzer is composed by several smaller analyzer-like components)
    2. AnalyzerVerifier may throw some exceptions because it test Diagnostics. But it should be optional because analyzer-like smaller components may not need it. If it is not optional the tests for the components become to need to wrap try-catch statements for each call of VerifyAnalyzerAsync
  • Test Doubles for DiagnosticAnalyzer

    • NullAnalyzer: it do nothing
    • StubAnalyzer: it analyze codes with a Dena.CodeAnalysis.Testing.AnalyzerActions
    • SpyAnalyzer: it analyze codes and do not report any Diagnostics, but instead it records all actions that registered via Microsoft.CodeAnalysis.Dignostics.AnalysisContext

Requirements

  • .NET Standard 2.1 or later

Usage

Run DiagnosticAnalyzer

var analyzer = new YourAnalyzer();

// The analyzer get intialized and get to call registered actions.
await DiagnosticAnalyzerRunner.Run(
    analyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Get Diagnostics

var analyzer = new YourAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    analyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Assert.AreEqual(0, diagnostics.Length);

Assert Locations

var location = diagnostic.Location;

LocationAssert.HaveTheSpan(
    "/0/Test0.",             // Optional. Skip path assertion if the path not specified,  
    new LinePosition(1, 0),
    new LinePosition(8, 5),
    location
);

Print Diagnostics

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    anyAnalyzer,
    @"
internal static class Foo
{
    internal static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}
ERROR");

Assert.AreEqual(0, diagnostics.Length, DiagnosticsFormatter.Format(diagnostics));
// This message is like:
//
//   // /0/Test0.cs(9,1): error CS0116: A namespace cannot directly contain members such as fields or methods
//   DiagnosticResult.CompilerError(""CS0116"").WithSpan(""/0/Test0.cs"", 9, 1, 9, 6),

Check whether the DiagnosticAnalyzer.Initialize have been called

var spyAnalyzer = new SpyAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    spyAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Assert.IsTrue(spyAnalyzer.IsInitialized);

Check recorded actions

var spyAnalyzer = new SpyAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    spyAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

// CompilationActionHistory hold the Compilation object that given
// to the action registered by AnalysisContext.RegisterCompilationAction.
Assert.AreEqual(1, spyAnalyzer.CompilationActionHistory.Count);

// Other available histories are:
//
//   - spyAnalyzer.CodeBlockActionHistory
//   - spyAnalyzer.CodeBlockStartActionHistory
//   - spyAnalyzer.CompilationActionHistory
//   - spyAnalyzer.CompilationStartActionHistory
//   - spyAnalyzer.OperationActionHistory
//   - spyAnalyzer.OperationBlockActionHistory
//   - spyAnalyzer.OperationBlockStartAction
//   - spyAnalyzer.OperationBlockStartActionHistory
//   - spyAnalyzer.SemanticModelActionHistory
//   - spyAnalyzer.SymbolActionHistory
//   - spyAnalyzer.SymbolStartActionHistory
//   - spyAnalyzer.SyntaxNodeActionHistory
//   - spyAnalyzer.SyntaxTreeActionHistory

Do something in action

var stubAnalyzer = new StubAnalyzer(
    new AnalyzerActions
    {
        CodeBlockStartAction = context => DoSomething()
    }
);

await DiagnosticAnalyzerRunner.Run(
    stubAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

// Other available actions are:
//
//   - stubAnalyzer.CodeBlockAction
//   - stubAnalyzer.CodeBlockStartAction
//   - stubAnalyzer.CompilationAction
//   - stubAnalyzer.CompilationStartAction
//   - stubAnalyzer.OperationAction
//   - stubAnalyzer.OperationBlockAction
//   - stubAnalyzer.OperationBlockStartAction
//   - stubAnalyzer.OperationBlockStartAction
//   - stubAnalyzer.SemanticModelAction
//   - stubAnalyzer.SymbolAction
//   - stubAnalyzer.SymbolStartAction
//   - stubAnalyzer.SyntaxNodeAction
//   - stubAnalyzer.SyntaxTreeAction

License

MIT license

Dena.CodeAnalysis.Testing

NuGet version
CircleCI

This library provides TDD friendly DiagnosticAnalyzer test helpers:

  • DiagnosticAnalyzerRunner

    A runner for Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer.
    The purpose of the runner is providing another runner instead of Microsoft.CodeAnalysis.Analyzer.Testing.AnalyzerVerifier.VerifyAnalyzerAsync.

    Because of the AnalyzerVerifier has several problems:

    1. Using AnalyzerVerifier, it is hard to instantiate analyzer with custom arguments (the custom args may be needed if your analyzer is composed by several smaller analyzer-like components)
    2. AnalyzerVerifier may throw some exceptions because it test Diagnostics. But it should be optional because analyzer-like smaller components may not need it. If it is not optional the tests for the components become to need to wrap try-catch statements for each call of VerifyAnalyzerAsync
  • Test Doubles for DiagnosticAnalyzer

    • NullAnalyzer: it do nothing
    • StubAnalyzer: it analyze codes with a Dena.CodeAnalysis.Testing.AnalyzerActions
    • SpyAnalyzer: it analyze codes and do not report any Diagnostics, but instead it records all actions that registered via Microsoft.CodeAnalysis.Dignostics.AnalysisContext

Requirements

  • .NET Standard 2.1 or later

Usage

Run DiagnosticAnalyzer

var analyzer = new YourAnalyzer();

// The analyzer get intialized and get to call registered actions.
await DiagnosticAnalyzerRunner.Run(
    analyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Get Diagnostics

var analyzer = new YourAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    analyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Assert.AreEqual(0, diagnostics.Length);

Assert Locations

var location = diagnostic.Location;

LocationAssert.HaveTheSpan(
    "/0/Test0.",             // Optional. Skip path assertion if the path not specified,  
    new LinePosition(1, 0),
    new LinePosition(8, 5),
    location
);

Print Diagnostics

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    anyAnalyzer,
    @"
internal static class Foo
{
    internal static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}
ERROR");

Assert.AreEqual(0, diagnostics.Length, DiagnosticsFormatter.Format(diagnostics));
// This message is like:
//
//   // /0/Test0.cs(9,1): error CS0116: A namespace cannot directly contain members such as fields or methods
//   DiagnosticResult.CompilerError(""CS0116"").WithSpan(""/0/Test0.cs"", 9, 1, 9, 6),

Check whether the DiagnosticAnalyzer.Initialize have been called

var spyAnalyzer = new SpyAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    spyAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

Assert.IsTrue(spyAnalyzer.IsInitialized);

Check recorded actions

var spyAnalyzer = new SpyAnalyzer();

var diagnostics = await DiagnosticAnalyzerRunner.Run(
    spyAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

// CompilationActionHistory hold the Compilation object that given
// to the action registered by AnalysisContext.RegisterCompilationAction.
Assert.AreEqual(1, spyAnalyzer.CompilationActionHistory.Count);

// Other available histories are:
//
//   - spyAnalyzer.CodeBlockActionHistory
//   - spyAnalyzer.CodeBlockStartActionHistory
//   - spyAnalyzer.CompilationActionHistory
//   - spyAnalyzer.CompilationStartActionHistory
//   - spyAnalyzer.OperationActionHistory
//   - spyAnalyzer.OperationBlockActionHistory
//   - spyAnalyzer.OperationBlockStartAction
//   - spyAnalyzer.OperationBlockStartActionHistory
//   - spyAnalyzer.SemanticModelActionHistory
//   - spyAnalyzer.SymbolActionHistory
//   - spyAnalyzer.SymbolStartActionHistory
//   - spyAnalyzer.SyntaxNodeActionHistory
//   - spyAnalyzer.SyntaxTreeActionHistory

Do something in action

var stubAnalyzer = new StubAnalyzer(
    new AnalyzerActions
    {
        CodeBlockStartAction = context => DoSomething()
    }
);

await DiagnosticAnalyzerRunner.Run(
    stubAnalyzer,
    @"public static class Foo
{
    public static void Bar()
    {
        System.Console.WriteLine(""Hello, World!"");
    }
}");

// Other available actions are:
//
//   - stubAnalyzer.CodeBlockAction
//   - stubAnalyzer.CodeBlockStartAction
//   - stubAnalyzer.CompilationAction
//   - stubAnalyzer.CompilationStartAction
//   - stubAnalyzer.OperationAction
//   - stubAnalyzer.OperationBlockAction
//   - stubAnalyzer.OperationBlockStartAction
//   - stubAnalyzer.OperationBlockStartAction
//   - stubAnalyzer.SemanticModelAction
//   - stubAnalyzer.SymbolAction
//   - stubAnalyzer.SymbolStartAction
//   - stubAnalyzer.SyntaxNodeAction
//   - stubAnalyzer.SyntaxTreeAction

License

MIT license

Dependencies

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version History

Version Downloads Last updated
1.0.0 72 2/22/2021
1.0.0-alpha3 51 2/19/2021
1.0.0-alpha2 49 2/19/2021
1.0.0-alpha1 47 2/19/2021
1.0.0-alpha0 52 2/19/2021