Search code examples
c#roslynsourcegenerators

How to unit test SourceGenerator?


I writed a SourceGenerator, but how do I test it?

Main issue is how to imitate GeneratorExecutionContext (or just Compilation inside it) which generator gets into Execute method. I think there is a proper way to make fake SyntaxTrees for unit testing, but I cant find it. There are many articles about source generators itself, but none of them explain how to test generators.


Solution

  • You should look at official Source Generators Cookbook

    There is example from it:

    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    
    namespace GeneratorTests.Tests
    {
        [TestClass]
        public class GeneratorTests
        {
            [TestMethod]
            public void SimpleGeneratorTest()
            {
                // Create the 'input' compilation that the generator will act on
                Compilation inputCompilation = CreateCompilation(@"
    namespace MyCode
    {
        public class Program
        {
            public static void Main(string[] args)
            {
            }
        }
    }
    ");
    
                // directly create an instance of the generator
                // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime)
                CustomGenerator generator = new CustomGenerator();
    
                // Create the driver that will control the generation, passing in our generator
                GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
    
                // Run the generation pass
                // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
                driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);
    
                // We can now assert things about the resulting compilation:
                Debug.Assert(diagnostics.IsEmpty); // there were no diagnostics created by the generators
                Debug.Assert(outputCompilation.SyntaxTrees.Count() == 2); // we have two syntax trees, the original 'user' provided one, and the one added by the generator
                Debug.Assert(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics
    
                // Or we can look at the results directly:
                GeneratorDriverRunResult runResult = driver.GetRunResult();
    
                // The runResult contains the combined results of all generators passed to the driver
                Debug.Assert(runResult.GeneratedTrees.Length == 1);
                Debug.Assert(runResult.Diagnostics.IsEmpty);
    
                // Or you can access the individual results on a by-generator basis
                GeneratorRunResult generatorResult = runResult.Results[0];
                Debug.Assert(generatorResult.Generator == generator);
                Debug.Assert(generatorResult.Diagnostics.IsEmpty);
                Debug.Assert(generatorResult.GeneratedSources.Length == 1);
                Debug.Assert(generatorResult.Exception is null);
            }
    
            private static Compilation CreateCompilation(string source)
                => CSharpCompilation.Create("compilation",
                    new[] { CSharpSyntaxTree.ParseText(source) },
                    new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
                    new CSharpCompilationOptions(OutputKind.ConsoleApplication));
        }
    }