Skip to content
Asserts for testing Roslyn analyzers.
C# PowerShell
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
AstView
Gu.Roslyn.Asserts.Analyzers.Tests
Gu.Roslyn.Asserts.Analyzers.Vsix
Gu.Roslyn.Asserts.Analyzers
Gu.Roslyn.Asserts.Tests.Net472WithAttributes
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 Gitter

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

RoslynAssert.Valid

Use RoslynAssert.Valid<NopAnalyzer>(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 Valid
{
    private static readonly DiagnosticAnalyzer Analyzer = new YourAnalyzer();

    [Test]
    public void M()
    {
        var code = @"
namespace N
{
    class C
    {
    }
}";
        RoslynAssert.Valid(Analyzer, code);
    }
    ...
}

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

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

    [Test]
    public void M()
    {
        var code = @"
namespace N
{
    class C
    {
    }
}";
        RoslynAssert.Valid(Analyzer, Descriptor, code);
    }
    ...
}

When testing all analyzers something like this can be used:

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


    private static readonly Solution ValidProjectSln = CodeFactory.CreateSolution(
        ProjectFile.Find("Valid.csproj"),
        AllAnalyzers,
        RoslynAssert.MetadataReferences);

    [TestCaseSource(nameof(AllAnalyzers))]
    public void Valid(DiagnosticAnalyzer analyzer)
    {
        RoslynAssert.Valid(analyzer, ValidProjectSln);
    }
}

RoslynAssert.Diagnostics

Use RoslynAssert.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 M()
    {
        var code = @"
namespace N
{
    class ↓C
    {
    }
}";
        RoslynAssert.Diagnostics(Analyzer, code);
    }

    [Test]
    public void M()
    {
        var code = @"
namespace N
{
    class ↓C
    {
    }
}";
        RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic.WithMessage("Don't name it C"), 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 M()
    {
        var code = @"
namespace N
{
    class ↓Foo
    {
    }
}";
        RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
    }
    ...
}

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

CodeFix

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

public class CodeFix
{
    private static readonly DiagnosticAnalyzer Analyzer = new FieldNameMustNotBeginWithUnderscore();
    private static readonly CodeFixProvider Fix = new SA1309CodeFixProvider();

	[Test]
	public void M()
	{
		var before = @"
namespace N
{
	class C
	{
		private readonly int ↓_value;
	}
}";

		var after = @"
namespace N
{
	class C
	{
		private readonly int value;
	}
}";
		RoslynAssert.CodeFix(Analyzer, Fix, before, after);
	}
}

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 M1()
    {
        var before = @"
namespace N
{
    class C
    {
        private readonly int ↓_value;
    }
}";

        var after = @"
namespace N
{
    class C
    {
        private readonly int value;
    }
}";
        RoslynAssert.CodeFix(Analyzer, Fix, before, after);
    }
}

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

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 M1()
    {
        var before = @"
namespace N
{
    class C
    {
        private readonly int ↓_value;
    }
}";

        var after = @"
namespace N
{
    class C
    {
        private readonly int value;
    }
}";
        RoslynAssert.CodeFix(Analyzer, Fix, before, after, fixTitle: "Don't use underscore prefix");
    }
    ...
}

If the analyzer supports many diagnostics the overload with ExpectedDiagnostic must be used. This suppresses all diagnostics 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.

public class CodeFix
{
    private static readonly CodeFixProvider Fix = new RemoveUnusedFixProvider();
    private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create("CS0067");

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

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

		var after = @"
namespace N
{
	using System;

	public class C
	{
	}
}";
		RoslynAssert.CodeFix(Fix, ExpectedDiagnostic, before, after);
	}
}

FixAll

When there are many issues that will be fixed: RoslynAssert.FixAll does:

  • Fix all diagnostics one by one
  • Fix all diagnostics in all supported scopes.
public class CodeFix
{
    private static readonly DiagnosticAnalyzer Analyzer = new FieldNameMustNotBeginWithUnderscore();
    private static readonly CodeFixProvider Fix = new SA1309CodeFixProvider();

	[Test]
	public void M()
	{
		var before = @"
namespace N
{
	class C
	{
		private readonly int ↓_value1;
		private readonly int ↓_value2;
	}
}";

		var after = @"
namespace N
{
	class C
	{
		private readonly int value1;
		private readonly int value2;
	}
}";
		RoslynAssert.FixAll(Analyzer, Fix, before, after);
	}
}

NoFix

Test that the analyzer reports an error or warning at position indicated with ↓ and that the code fix 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.

public class CodeFix
{
    private static readonly DiagnosticAnalyzer Analyzer = new FieldNameMustNotBeginWithUnderscore();
    private static readonly CodeFixProvider Fix = new SA1309CodeFixProvider();

	[Test]
	public void M()
	{
		var code = @"
namespace N
{
	class C
	{
		private readonly int ↓_value;
	}
}";

		RoslynAssert.NoFix(Analyzer, Fix, code);
	}
}

Refactoring

public class CodeFix
{
	private static readonly CodeRefactoringProvider Refactoring = new ClassNameToUpperCaseRefactoringProvider();

    [Test]
    public void M()
    {
        var before = @"
class ↓c
{
}";

        var after = @"
class C
{
}";
        RoslynAssert.Refactoring(Refactoring, before, after);
		// Or if you want to assert on title also
		RoslynAssert.Refactoring(Refactoring, before, after, title: "Change to uppercase.");
    }
}

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");
    RoslynAssert.Ast(expected, actual);
}

SyntaxFactoryWriter

Get a string with a call to SyntaxFactory for generating the code passed in.

var code = @"namespace A.B
{
    public class C
    {
    }
}";
var call = SyntaxFactoryWriter.Serialize(code);

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: If you have an AssemblyInfo.cs you can put it there. If not you can put it in any .cs file for example AssemblyAttributes.cs

Sample AssemblyInfo.cs (for the test project.)

using Gu.Roslyn.Asserts;


[assembly: SuppressWarnings("CS1701")]
[assembly: TransitiveMetadataReferences(typeof(TypeInTestProject))]

// Below should not be needed, prefer TransitiveMetadataReferences as it updates as the resolution graph changes.
[assembly: MetadataReference(typeof(object), new[] { "global", "corlib" })]
[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))]

TransitiveMetadataReferences

For specifying a metadata reference to be used in the tests. Pass in a type in a top level assembly and let the attribute figure out all transitive dependencies.

[assembly: TransitiveMetadataReferences(typeof(TypeInTestProject))]

Or provide many top level types:

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

MetadataReference

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

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

MetadataReferences

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 RoslynAssert.ResetMetadataReferences() resets RoslynAssert.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());

SuppressWarnings

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

[assembly: SuppressWarnings("CS1701")]

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 N
{
    class Foo
    {
        private readonly int _value;
    }
}";

    var after = @"
namespace N
{
    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 DoNotUseUnderscoreFix(), diagnostics);
    CodeAssert.AreEqual(after, fixedSln.Projects.Single().Documents.Single());
}

CodeFactory

CreateSolution

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

[Test]
public void CreateSolutionFromSources()
{
    var code = @"
namespace N
{
    class Foo
    {
        private readonly int _value;
    }
}";
    var sln = CodeFactory.CreateSolution(code, new[] { new FieldNameMustNotBeginWithUnderscore() });
    Assert.AreEqual("N", 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 N
{
    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());
}

AstView

Animation

Usage with different test project types

Net472 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.