Skip to content
Asserts for testing Roslyn analyzers.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.paket
Gu.Roslyn.Asserts.Tests.Net46WithAttributes
Gu.Roslyn.Asserts.Tests.NetCoreWithAttributes
Gu.Roslyn.Asserts.Tests
Gu.Roslyn.Asserts
TestApps
.editorconfig
.gitignore
Gu.Roslyn.Asserts.sln
Gu.Roslyn.Asserts.sln.DotSettings
Gu.Roslyn.Asserts.snk
LICENSE
README.md
RELEASE_NOTES.md
Settings.XamlStyler
appveyor.yml
paket.dependencies
paket.lock

README.md

Gu.Roslyn.Asserts

License NuGet Build status

Microsoft are working on an official package for testing analyzers: Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.

Hopefully this library will not be needed in the future.

Asserts for testing Roslyn analyzers.

Use 1.x for Microsoft.CodeAnalysis 1.x

AnalyzerAssert.Valid

Use AnalyzerAssert.Valid<NoErrorAnalyzer>(code) to test that an analyzer does not report errors for valid code. The code is checked so that it does not have any compiler errors either. A typical test fixture looks like:

public class ValidCode
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class Foo
    {
    }
}";
        AnalyzerAssert.Valid(YourAnalyzer, code);
    }
    ...
}

If the analyzer produces many diagnostics you can pass in a descriptor so that only diagnostics matching it are checked.

public class ValidCode
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly DiagnosticDescriptor Descriptor = YourAnalyzer.SomeDescriptor;

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class Foo
    {
    }
}";
        AnalyzerAssert.Valid(YourAnalyzer, Descriptor, code);
    }
    ...
}

When testing all analyzers something like this can be used:

public class ValidCodeWithAllAnalyzers
{
    private static readonly IReadOnlyList<DiagnosticAnalyzer> AllAnalyzers = typeof(KnownSymbol)
                                                                                .Assembly.GetTypes()
                                                                                .Where(typeof(DiagnosticAnalyzer).IsAssignableFrom)
                                                                                .Select(t => (DiagnosticAnalyzer)Activator.CreateInstance(t))
                                                                                .ToArray();


    private static readonly Solution ValidCodeProjectSln = CodeFactory.CreateSolution(
        ProjectFile.Find("ValidCode.csproj"),
        AllAnalyzers,
        AnalyzerAssert.MetadataReferences);

    [TestCaseSource(nameof(AllAnalyzers))]
    public void ValidCodeProject(DiagnosticAnalyzer analyzer)
    {
        AnalyzerAssert.Valid(analyzer, ValidCodeProjectSln);
    }
}

AnalyzerAssert.Diagnostics

Use AnalyzerAssert.Diagnostics<FieldNameMustNotBeginWithUnderscore>(code) to test that the analyzer reports error or warning at position indicated with ↓ With an aplhanumeric keyboard alt + 25 writes .

A typical test fixture looks like:

public class Diagnostics
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        AnalyzerAssert.Diagnostics(YourAnalyzer, code);
    }

    [Test]
    public void CheckMessageAlso()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        AnalyzerAssert.Diagnostics(YourAnalyzer, ExpectedDiagnostic.WithMessage("Don't name it foo"), code);
    }
    ...
}

If the analyzer produces many diagnostics you can pass in a descriptor so that only diagnostics matching it are checked.

public class Diagnostics
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace TestCode
{
    class ↓Foo
    {
    }
}";
        AnalyzerAssert.Diagnostics(YourAnalyzer, ExpectedDiagnostic, code);
    }
    ...
}

If the analyzer supports many diagnostics the overload with ExpectedDiagnostic must be used. This suppresses all diagnsics other than the expected.

CodeFix

Test that the analyzer reports an error or warning at position indicated with ↓ and that the codefix fixes it and produces the expected code. With an aplhanumeric keyboard alt + 25 writes .

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
    AnalyzerAssert.CodeFix<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code, fixedCode);
}

A typical test fixture looks like:

public class CodeFix
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();
    private static readonly CodeFixProvider Fix = new YorCodeFixProvider();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(YourAnalyzer.Descriptor);

    [Test]
    public void SomeTest()
    {
        var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

        var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
        AnalyzerAssert.CodeFix(Analyzer, Fix, code, fixedCode);
    }

    [Test]
    public void ExplicitFixTitle()
    {
        var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

        var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
        AnalyzerAssert.CodeFix(Analyzer, Fix, code, fixedCode, fixTitle: "Don't use underscore prefix");
    }
    ...
}

With explicit title for the fix to apply. Useful when there are many candidate fixes.

If the analyzer supports many diagnostics the overload with ExpectedDiagnostic must be used. This suppresses all diagnsics other than the expected.

Code fix only

When the code fix is for a warning produced by an analyzer that you do not own, for example a built in analyzer in Visual Studio.

[Test]
public void TestThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    using System;

    public class Foo
    {
        public event EventHandler ↓Bar;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    using System;

    public class Foo
    {
    }
}";
    AnalyzerAssert.CodeFix<RemoveUnusedFixProvider>("CS0067", code, fixedCode);
}

FixAll

When there are many isses that will be fixed:

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixProducesExpectedCode()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value1;
        private readonly int ↓_value2;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value1;
        private readonly int value2;
    }
}";
    AnalyzerAssert.FixAll<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code, fixedCode);
}

NoFix

Test that the analyzer reports an error or warning at position indicated with ↓ and that the codefix does not change the code. With an aplhanumeric keyboard alt + 25 writes . This can happen if for example it is decided to not support rare edge cases with the code fix.

[Test]
public void TestThatAnalyzerWarnsOnCorrectPositionAndThatCodeFixDoesNothing()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int ↓_value;
    }
}";

    AnalyzerAssert.NoFix<FieldNameMustNotBeginWithUnderscore, SA1309CodeFixProvider>(code);
}

Refactoring

        [Test]
        public void WithPositionIndicated()
        {
            var testCode = @"
class ↓Foo
{
}";

            var fixedCode = @"
class FOO
{
}";

            AnalyzerAssert.Refactoring(new ClassNameToUpperCaseRefactoringProvider(), testCode, fixedCode);
        }

AST

For checking every node and token in the tree.

[Test]
public void CheckAst()
{
    var actual = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName("a"), SyntaxFactory.IdentifierName("b"));
    var expected = CSharpSyntaxTree.ParseText("var c = a + b").FindAssignmentExpression("a + b");
    AnalyzerAssert.Ast(expected, actual);
}

Attributes

When creating the workspace to analyze metadata references need to be added. There are a couple of ways to provide them using this library. Some overloads of the asserts allow passing explicit references but it will be verbose to do that everywhere.

In most scenarios something like this in the test project is what you want:

using Gu.Roslyn.Asserts;

[assembly: TransitiveMetadataReferences(
    typeof(Microsoft.EntityFrameworkCore.DbContext),
    typeof(Microsoft.AspNetCore.Mvc.Controller))]

MetadataReferenceAttribute

For specifying a metadata reference to be used in the tests, with or without aliases.

[assembly: MetadataReference(typeof(object), new[] { "global", "corlib" })]

MetadataReferencesAttribute

For specifying a batch of metadata references to be used in the tests.

[assembly: MetadataReferences(
    typeof(System.Linq.Enumerable),
    typeof(System.Net.WebClient),
    typeof(System.Reactive.Disposables.SerialDisposable),
    typeof(System.Reactive.Disposables.ICancelable),
    typeof(System.Reactive.Linq.Observable),
    typeof(Gu.Reactive.Condition),
    typeof(Gu.Wpf.Reactive.ConditionControl),
    typeof(System.Xml.Serialization.XmlSerializer),
    typeof(System.Windows.Media.Matrix),
    typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation),
    typeof(Microsoft.CodeAnalysis.Compilation),
    typeof(NUnit.Framework.Assert))]

Calling AnalyzerAssert.ResetMetadataReferences() resets AnalyzerAssert.MetadataReferences to the list provided via the attribute or clears it if no attribute is provided.

MetadataReferences

For getting all metadata references specified with attributes use:

var compilation = CSharpCompilation.Create(
    "TestProject",
    new[] { syntaxTree },
    MetadataReferences.FromAttributes());

Sample AssemblyInfo.cs (for the test project.)

using System.Reflection;
using System.Runtime.InteropServices;
using Gu.Roslyn.Asserts;

...
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: MetadataReference(typeof(object), new[] { "global", "corlib" })]
[assembly: MetadataReference(typeof(System.Diagnostics.Debug), new[] { "global", "system" })]
[assembly: MetadataReferences(
    typeof(System.Linq.Enumerable),
    typeof(System.Net.WebClient),
    typeof(System.Data.Common.DbConnection),
    typeof(System.Reactive.Disposables.SerialDisposable),
    typeof(System.Reactive.Disposables.ICancelable),
    typeof(System.Reactive.Linq.Observable),
    typeof(System.Xml.Serialization.XmlSerializer),
    typeof(System.Windows.Media.Brush),
    typeof(System.Windows.Controls.Control),
    typeof(System.Windows.Media.Matrix),
    typeof(System.Xaml.XamlLanguage),
    typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation),
    typeof(Microsoft.CodeAnalysis.Compilation),
    typeof(NUnit.Framework.Assert))]

Exlicit set AnalyzerAssert.MetadataReferences

AnalyzerAssert.MetadataReferences.Add(MetadataReference.CreateFromFile(typeof(int).Assembly.Location));

A helper like this can be used.

private static IReadOnlyList<MetadataReference> CreateMetadataReferences(params Type[] types)
{
    return types.Select(type => type.GetTypeInfo().Assembly)
                .Distinct()
                .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
                .ToArray();
}

IgnoredErrorsAttribute

For globally ignoring compiler warnings and errors introduced by code fixes when calling calling AnalyzerAssert.CodeFix and AnalyzerAssert.FixAll.

[assembly: IgnoredErrors("CS1569", ...)]

AllowedDiagnosticsAttribute

For globally ignoring compiler warnings and errors introduced by code fixes when calling calling AnalyzerAssert.CodeFix and AnalyzerAssert.FixAll.

[assembly: AllowedDiagnostics(AllowedDiagnostics.Warnings)]

Analyze

GetDiagnosticsAsync

Analyze a cs, csproj or sln file on disk.

[Test]
public async Task GetDiagnosticsFromProjectOnDisk()
{
    var dllFile = new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath;
    Assert.AreEqual(true, CodeFactory.TryFindProjectFile(new FileInfo(dllFile), out FileInfo projectFile));
    var diagnostics = await Analyze.GetDiagnosticsAsync(new FieldNameMustNotBeginWithUnderscore(), projectFile, MetadataReferences)
                                    .ConfigureAwait(false);
    ...
}

Fix

When dropping down to manual mode Analyze & Fix can be used like this:

[Test]
public void SingleClassOneErrorCorrectFix()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int _value;
    }
}";

    var fixedCode = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int value;
    }
}";
    var analyzer = new FieldNameMustNotBeginWithUnderscore();
    var cSharpCompilationOptions = CodeFactory.DefaultCompilationOptions(analyzer);
    var metadataReferences = new[] { MetadataReference.CreateFromFile(typeof(int).Assembly.Location) };
    var sln = CodeFactory.CreateSolution(code, cSharpCompilationOptions, metadataReferences);
    var diagnostics = Analyze.GetDiagnostics(sln, analyzer);
    var fixedSln = Fix.Apply(sln, new DontUseUnderscoreCodeFixProvider(), diagnostics);
    CodeAssert.AreEqual(fixedCode, fixedSln.Projects.Single().Documents.Single());
}

CodeFactory

CreateSolution

Create a Microsoft.CodeAnalysis.AdhocWorkspace, a Roslyn Solution from code.

[Test]
public void CreateSolutionFromSources()
{
    var code = @"
namespace RoslynSandbox
{
    class Foo
    {
        private readonly int _value;
    }
}";
    var sln = CodeFactory.CreateSolution(code, new[] { new FieldNameMustNotBeginWithUnderscore() });
    Assert.AreEqual("RoslynSandbox", sln.Projects.Single().Name);
    Assert.AreEqual("Foo.cs", sln.Projects.Single().Documents.Single().Name);
}

[Test]
public void CreateSolutionFromSources()
{
    var code1 = @"
namespace Project1
{
    class Foo1
    {
        private readonly int _value;
    }
}";

    var code2 = @"
namespace Project2
{
    class Foo2
    {
        private readonly int _value;
    }
}";
    var sln = CodeFactory.CreateSolution(new[] { code1, code2 }, new[] { new FieldNameMustNotBeginWithUnderscore() });
    CollectionAssert.AreEqual(new[] { "Project1", "Project2" }, sln.Projects.Select(x => x.Name));
    Assert.AreEqual(new[] { "Foo1.cs", "Foo2.cs" }, sln.Projects.Select(x => x.Documents.Single().Name));
}

Create a Microsoft.CodeAnalysis.AdhocWorkspace, a Roslyn Solution from a file on disk.

[Test]
public void CreateSolutionFromProjectFile()
{
    Assert.AreEqual(
        true,
        CodeFactory.TryFindProjectFile(
            new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath),
            out FileInfo projectFile));
    var solution = CodeFactory.CreateSolution(
        projectFile,
        new[] { new FieldNameMustNotBeginWithUnderscore(), },
        CreateMetadataReferences(typeof(object)));
}

[Test]
public void CreateSolutionFromSolutionFile()
{
    Assert.AreEqual(
        true,
        CodeFactory.TryFindFileInParentDirectory(
            new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute).LocalPath).Directory, "Gu.Roslyn.Asserts.sln",
            out FileInfo solutionFile));
    var solution = CodeFactory.CreateSolution(
        solutionFile,
        new[] { new FieldNameMustNotBeginWithUnderscore(), },
        CreateMetadataReferences(typeof(object)));
}

Benchmark

Sample benchmark using BenchmarkDotNet.

public class FieldNameMustNotBeginWithUnderscoreBenchmark
{
    private static readonly Solution Solution = CodeFactory.CreateSolution(
        CodeFactory.FindSolutionFile("Gu.Roslyn.Asserts.sln"),
        MetadataReferences.Transitive(typeof(Benchmark).Assembly).ToArray());

    private static readonly Benchmark Benchmark = Benchmark.Create(Solution, new FieldNameMustNotBeginWithUnderscore());

    [BenchmarkDotNet.Attributes.Benchmark]
    public void RunOnGuRoslynAssertsSln()
    {
        Benchmark.Run();
    }
}

SyntaxNodeExt

[Test]
public void FindAssignmentExpressionDemo()
{
    var syntaxTree = CSharpSyntaxTree.ParseText(
        @"
namespace RoslynSandbox
{
    internal class Foo
    {
        internal Foo()
        {
            var temp = 1;
            temp = 2;
        }
    }
}");
    var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), });
    var semanticModel = compilation.GetSemanticModel(syntaxTree);
    var assignment = syntaxTree.FindAssignmentExpression("temp = 2");
    Assert.AreEqual("temp = 2", assignment.ToString());
    Assert.AreEqual("int", semanticModel.GetTypeInfo(assignment.Right).Type.ToDisplayString());
}

Usage with different test project types

Net461 new project type.

<PropertyGroup>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>

NetCoreApp2.0

TODO figure out what is needed here.

You can’t perform that action at this time.