diff --git a/Directory.Packages.props b/Directory.Packages.props index ed7b11c3a3..4c429e7eaf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,7 @@  + @@ -25,6 +26,7 @@ + @@ -33,6 +35,7 @@ + diff --git a/Readme.md b/Readme.md index bad6b74b46..22b8b6cbc4 100644 --- a/Readme.md +++ b/Readme.md @@ -20,6 +20,7 @@ | ------- | ----- | | Rocket.Surgery.Extensions.Testing | [![nuget-version-ingbk+ngdt+w-badge]![nuget-downloads-ingbk+ngdt+w-badge]][nuget-ingbk+ngdt+w] | | Rocket.Surgery.Extensions.Testing.Analyzers | [![nuget-version-is2d/pp3l2nq-badge]![nuget-downloads-is2d/pp3l2nq-badge]][nuget-is2d/pp3l2nq] | +| Rocket.Surgery.Extensions.Testing.AutoFixtures | [![nuget-version-zo+gi4/wtnoq-badge]![nuget-downloads-zo+gi4/wtnoq-badge]][nuget-zo+gi4/wtnoq] | | Rocket.Surgery.Extensions.Testing.Coverlet | [![nuget-version-2jdbmqdcrhfg-badge]![nuget-downloads-2jdbmqdcrhfg-badge]][nuget-2jdbmqdcrhfg] | | Rocket.Surgery.Extensions.Testing.FakeItEasy | [![nuget-version-6rnnzg4ixtvq-badge]![nuget-downloads-6rnnzg4ixtvq-badge]][nuget-6rnnzg4ixtvq] | | Rocket.Surgery.Extensions.Testing.Fixtures | [![nuget-version-xegxxxxh/pzg-badge]![nuget-downloads-xegxxxxh/pzg-badge]][nuget-xegxxxxh/pzg] | @@ -49,6 +50,9 @@ TBD [nuget-is2d/pp3l2nq]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing.Analyzers/ [nuget-version-is2d/pp3l2nq-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.Analyzers.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-is2d/pp3l2nq-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.Analyzers.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" +[nuget-zo+gi4/wtnoq]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing.AutoFixtures/ +[nuget-version-zo+gi4/wtnoq-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.AutoFixtures.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" +[nuget-downloads-zo+gi4/wtnoq-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.AutoFixtures.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" [nuget-2jdbmqdcrhfg]: https://www.nuget.org/packages/Rocket.Surgery.Extensions.Testing.Coverlet/ [nuget-version-2jdbmqdcrhfg-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Extensions.Testing.Coverlet.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-2jdbmqdcrhfg-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Extensions.Testing.Coverlet.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" diff --git a/Testing.sln b/Testing.sln index 10decd3f78..351ec5df30 100644 --- a/Testing.sln +++ b/Testing.sln @@ -98,6 +98,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{2E4E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.Testing.SourceGenerators", "src\Testing.SourceGenerators\Rocket.Surgery.Extensions.Testing.SourceGenerators.csproj", "{14E1380D-30CE-4EA6-8392-C9744B3548CC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests", "test\Testing.AutoFixtures.Tests\Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests.csproj", "{5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "node_modules", "node_modules", "{23F6FBFB-B51E-4BCC-BAFB-7FE48A105592}" + ProjectSection(SolutionItems) = preProject + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resolve", "resolve", "{55C6BB1F-E98B-48A8-93F4-1D0C3D6E1E93}" + ProjectSection(SolutionItems) = preProject + node_modules/resolve/.editorconfig = node_modules/resolve/.editorconfig + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "is-arrayish", "is-arrayish", "{82A69EC4-C831-4DAF-AE55-48CD28CE2F99}" + ProjectSection(SolutionItems) = preProject + node_modules/is-arrayish/.editorconfig = node_modules/is-arrayish/.editorconfig + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Extensions.Testing.AutoFixtures", "src\Testing.AutoFixtures\Rocket.Surgery.Extensions.Testing.AutoFixtures.csproj", "{3F1CF946-4DA3-4B96-8039-54C112BED0DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -258,6 +276,30 @@ Global {14E1380D-30CE-4EA6-8392-C9744B3548CC}.Release|x64.Build.0 = Release|Any CPU {14E1380D-30CE-4EA6-8392-C9744B3548CC}.Release|x86.ActiveCfg = Release|Any CPU {14E1380D-30CE-4EA6-8392-C9744B3548CC}.Release|x86.Build.0 = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|x64.Build.0 = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|x86.ActiveCfg = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Debug|x86.Build.0 = Debug|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|Any CPU.Build.0 = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|x64.ActiveCfg = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|x64.Build.0 = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|x86.ActiveCfg = Release|Any CPU + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8}.Release|x86.Build.0 = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|x64.Build.0 = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Debug|x86.Build.0 = Debug|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|Any CPU.Build.0 = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|x64.ActiveCfg = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|x64.Build.0 = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|x86.ActiveCfg = Release|Any CPU + {3F1CF946-4DA3-4B96-8039-54C112BED0DE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -280,6 +322,10 @@ Global {13ED04AE-B6C9-4D28-B768-2740E053BC69} = {A3232EC4-8418-4188-8EB8-DD463C031533} {2E4EA52F-4835-4D34-A6F5-650042213443} = {A3232EC4-8418-4188-8EB8-DD463C031533} {14E1380D-30CE-4EA6-8392-C9744B3548CC} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} + {5A08F7E7-90C1-4DAA-A732-4CD080AFE2E8} = {DF33E0FB-9790-4654-B60F-8AB22E0CC3D1} + {55C6BB1F-E98B-48A8-93F4-1D0C3D6E1E93} = {23F6FBFB-B51E-4BCC-BAFB-7FE48A105592} + {82A69EC4-C831-4DAF-AE55-48CD28CE2F99} = {23F6FBFB-B51E-4BCC-BAFB-7FE48A105592} + {3F1CF946-4DA3-4B96-8039-54C112BED0DE} = {8FFDF555-DB50-45F9-9A2D-6410F39151C3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {439897C2-CCBD-44FE-B2DC-A3E4670ADA59} diff --git a/src/Testing.AutoFixtures/AutoFixtureAttribute.cs b/src/Testing.AutoFixtures/AutoFixtureAttribute.cs new file mode 100644 index 0000000000..f257898e20 --- /dev/null +++ b/src/Testing.AutoFixtures/AutoFixtureAttribute.cs @@ -0,0 +1,19 @@ +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +internal static class AutoFixtureAttribute +{ + public const string Source = @"using System; +using System.Diagnostics; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixture +{ + [AttributeUsage(AttributeTargets.Class)] + [Conditional(""CODEGEN"")] + internal class AutoFixtureAttribute : Attribute + { + public AutoFixtureAttribute(Type type) => Type = type; + + public Type Type { get; } + } +}"; +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/AutoTestFixtureBuilderGenerator.cs b/src/Testing.AutoFixtures/AutoTestFixtureBuilderGenerator.cs new file mode 100644 index 0000000000..49d91ce75c --- /dev/null +++ b/src/Testing.AutoFixtures/AutoTestFixtureBuilderGenerator.cs @@ -0,0 +1,591 @@ +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +[Generator] +public partial class AutoFixtureGenerator : IIncrementalGenerator //, ISourceGenerator +{ + private static NamespaceDeclarationSyntax BuildNamespace(ISymbol namedTypeSymbol) + { + var displayString = namedTypeSymbol.ContainingNamespace.ToDisplayString() + ".Tests"; + return NamespaceDeclaration( + ParseName(displayString) + ) + .WithNamespaceKeyword( + Token( + TriviaList( + LineFeed + ), + SyntaxKind.NamespaceKeyword, + TriviaList( + Space + ) + ) + ) + .WithOpenBraceToken( + Token( + TriviaList(), + SyntaxKind.OpenBraceToken, + TriviaList( + LineFeed + ) + ) + ); + } + + private static ClassDeclarationSyntax BuildClassDeclaration(ISymbol namedTypeSymbol) + { + return ClassDeclaration( + Identifier( + TriviaList(), + $"{namedTypeSymbol.Name}{Fixture}", + TriviaList( + Space + ) + ) + ) + .WithModifiers( + TokenList( + [ + Token( + TriviaList( + LineFeed + ), + SyntaxKind.InternalKeyword, + TriviaList( + Space + ) + ), + Token( + TriviaList(), + SyntaxKind.SealedKeyword, + TriviaList( + Space + ) + ), + Token( + TriviaList(), + SyntaxKind.PartialKeyword, + TriviaList( + Space + ) + ), + ] + ) + ) + .WithKeyword( + Token( + TriviaList(), + SyntaxKind.ClassKeyword, + TriviaList( + Space + ) + ) + ) + .WithBaseList( + BaseList( + SingletonSeparatedList( + SimpleBaseType( + IdentifierName( + Identifier( + TriviaList(), + TestFixtureBuilder, + TriviaList( + LineFeed + ) + ) + ) + ) + ) + ) + .WithColonToken( + Token( + TriviaList(), + SyntaxKind.ColonToken, + TriviaList( + Space + ) + ) + ) + ) + .WithOpenBraceToken( + Token( + TriviaList(Tab), + SyntaxKind.OpenBraceToken, + TriviaList( + LineFeed + ) + ) + ) + .WithCloseBraceToken(Token(TriviaList(Tab), SyntaxKind.CloseBraceToken, TriviaList())) + .WithTrailingTrivia(LineFeed); + } + + private static MemberDeclarationSyntax BuildFields( + IParameterSymbol parameterSymbol, + InvocationExpressionSyntax invocationExpressionSyntax + ) + { + return FieldDeclaration( + VariableDeclaration( + IdentifierName( + Identifier( + TriviaList(), + parameterSymbol.Type.GetGenericDisplayName(), + TriviaList( + Space + ) + ) + ) + ) + .WithVariables( + SingletonSeparatedList( + VariableDeclarator( + Identifier( + TriviaList(), + $"_{parameterSymbol.Name}", + TriviaList( + Space + ) + ) + ) + .WithInitializer( + EqualsValueClause( + // TODO: [rlittlesii: February 29, 2024] Replace with FakeItEasy + invocationExpressionSyntax + ) + .WithEqualsToken( + Token( + TriviaList(), + SyntaxKind.EqualsToken, + TriviaList( + Space + ) + ) + ) + ) + ) + ) + ) + .WithModifiers( + TokenList( + Token( + TriviaList(), + SyntaxKind.PrivateKeyword, + TriviaList( + Space + ) + ) + ) + ) + .WithTrailingTrivia(LineFeed); + } + + private static MemberDeclarationSyntax BuildBuildMethod( + ISymbol namedTypeSymbol, + IEnumerable parameterSymbols + ) + { + var list = new List(); + foreach (var parameterSymbol in parameterSymbols) + { + list.Add(Argument(IdentifierName($"_{parameterSymbol.Name}"))); + list.Add(Token(SyntaxKind.CommaToken)); + } + + list.RemoveAt(list.Count - 1); + return GlobalStatement( + LocalFunctionStatement( + IdentifierName(namedTypeSymbol.Name), + Identifier("Build") + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.PrivateKeyword) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + ObjectCreationExpression( + IdentifierName(namedTypeSymbol.Name) + ) + .WithArgumentList( + ArgumentList( + SeparatedList(list) + ) + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ) + ); + } + + private static MemberDeclarationSyntax WithPropertyMethod(IParameterSymbol constructorParameter) + { + return GlobalStatement( + LocalFunctionStatement( + IdentifierName( + Identifier( + TriviaList(), + $"{constructorParameter.ContainingType.Name}{Fixture}", + TriviaList( + Space + ) + ) + ), + Identifier($"With{SplitLastCamel(constructorParameter)}") + ) + .WithModifiers( + TokenList( + Token( + TriviaList(), + SyntaxKind.PublicKeyword, + TriviaList( + Space + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter( + Identifier(constructorParameter.Name) + ) + .WithType( + IdentifierName( + Identifier( + TriviaList(), + constructorParameter.Type.GetGenericDisplayName(), + TriviaList( + Space + ) + ) + ) + ) + ) + ) + .WithCloseParenToken( + Token( + TriviaList(), + SyntaxKind.CloseParenToken, + TriviaList( + Space + ) + ) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ThisExpression(), + IdentifierName("With") + ) + ) + .WithArgumentList( + ArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Argument( + IdentifierName($"_{constructorParameter.Name}") + ) + .WithRefOrOutKeyword( + Token( + TriviaList(), + SyntaxKind.RefKeyword, + TriviaList( + Space + ) + ) + ), + Token( + TriviaList(), + SyntaxKind.CommaToken, + TriviaList( + Space + ) + ), + Argument( + IdentifierName(constructorParameter.Name) + ), + } + ) + ) + ) + ) + .WithArrowToken( + Token( + TriviaList(), + SyntaxKind.EqualsGreaterThanToken, + TriviaList( + Space + ) + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ) + .WithTrailingTrivia(LineFeed) + ); + } + + private static MemberDeclarationSyntax Operator(ISymbol namedTypeSymbol) + { + return ConversionOperatorDeclaration( + Token( + TriviaList(), + SyntaxKind.ImplicitKeyword, + TriviaList( + Space + ) + ), + IdentifierName(namedTypeSymbol.Name) + ) + .WithModifiers( + TokenList( + [ + Token( + TriviaList(), + SyntaxKind.PublicKeyword, + TriviaList( + Space + ) + ), + Token( + TriviaList(), + SyntaxKind.StaticKeyword, + TriviaList( + Space + ) + ), + ] + ) + ) + .WithOperatorKeyword( + Token( + TriviaList(), + SyntaxKind.OperatorKeyword, + TriviaList( + Space + ) + ) + ) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter( + Identifier(Fixture.ToLowerInvariant()) + ) + .WithType( + IdentifierName( + Identifier( + TriviaList(), + $"{namedTypeSymbol.Name}{Fixture}", + TriviaList( + Space + ) + ) + ) + ) + ) + ) + .WithCloseParenToken( + Token( + TriviaList(), + SyntaxKind.CloseParenToken, + TriviaList( + Space + ) + ) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(Fixture.ToLowerInvariant()), + IdentifierName("Build") + ) + ) + ) + .WithArrowToken( + Token( + TriviaList(), + SyntaxKind.EqualsGreaterThanToken, + TriviaList( + Space + ) + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ) + .WithTrailingTrivia(LineFeed); + } + + private static string SplitLastCamel(IParameterSymbol typeSymbol) + { + return Regex + .Replace(typeSymbol.Type.Name, "([A-Z])", " $1", RegexOptions.Compiled) + .Trim() + .Split(' ') + .Last(); + } + + private const string Fixture = nameof(Fixture); + + private const string TestFixtureBuilder = "ITestFixtureBuilder"; + + private InvocationExpressionSyntax GetFieldInvocation(Compilation compilation, IParameterSymbol symbol) + { + var fakeItEasy = compilation.GetTypeByMetadataName("FakeItEasy.Fake"); + + if (fakeItEasy is { }) + { + return InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("A"), + GenericName( + Identifier("Fake") + ) + .WithTypeArgumentList( + TypeArgumentListSyntax(symbol) + ) + ) + ); + } + + return InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName( + "Substitute" + ), + GenericName( + Identifier( + "For" + ) + ) + .WithTypeArgumentList( + TypeArgumentListSyntax( + symbol + ) + ) + ) + ); + + TypeArgumentListSyntax TypeArgumentListSyntax(IParameterSymbol parameterSymbol) + { + return TypeArgumentList( + SingletonSeparatedList( + ParseName(parameterSymbol.Type.GetGenericDisplayName()) + ) + ); + } + } + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var syntaxProvider = + context + .SyntaxProvider + .ForAttributeWithMetadataName( + "Rocket.Surgery.Extensions.Testing.AutoFixture.AutoFixtureAttribute", + (node, token) => node.IsKind(SyntaxKind.ClassDeclaration), + (syntaxContext, token) => syntaxContext + ) + .Combine(context.CompilationProvider); + + context.RegisterSourceOutput(syntaxProvider, GenerateFixtureBuilder); + + // do generator things + context.RegisterPostInitializationOutput( + initializationContext => + { + initializationContext.AddSource(nameof(AutoFixtureAttribute), AutoFixtureAttribute.Source); + initializationContext.AddSource(nameof(BuilderInterface), BuilderInterface.Source); + } + ); + + void GenerateFixtureBuilder( + SourceProductionContext productionContext, + (GeneratorAttributeSyntaxContext context, Compilation compilation) valueTuple + ) + { + ( var syntaxContext, var compilation ) = valueTuple; + + var substituteMetadata = compilation.GetTypeByMetadataName("NSubstitute.Substitute"); + var fakeItEasy = compilation.GetTypeByMetadataName("FakeItEasy.Fake"); + + if (syntaxContext.Attributes[0].ConstructorArguments[0].Value is not INamedTypeSymbol namedTypeSymbol) + { + return; + } + + var parameterSymbols = + namedTypeSymbol + .Constructors + .SelectMany(methodSymbol => methodSymbol.Parameters) + .Distinct(SymbolEqualityComparer.Default) + .OfType() + .ToList(); + + var fullList = + new[] { Operator(namedTypeSymbol), } + .Concat(parameterSymbols.Select(symbol => WithPropertyMethod(symbol))) + .Concat(FixtureWithMethods.BuildFixtureMethods(namedTypeSymbol)) + .Concat(new[] { BuildBuildMethod(namedTypeSymbol, parameterSymbols), }) + .Concat( + parameterSymbols.Select(symbol => BuildFields(symbol, GetFieldInvocation(compilation, symbol))) + ); + + var classDeclaration = BuildClassDeclaration(namedTypeSymbol) + .WithMembers(new(fullList)); + + // TODO: [rlittlesii: March 01, 2024] Configure use of same namespace, or define a namespace, or add suffix. + var namespaceDeclaration = BuildNamespace(namedTypeSymbol) + .WithMembers(new(classDeclaration)); + + var usings = + parameterSymbols + .Select(symbol => symbol.Type.ContainingNamespace.ToDisplayString()) + .Distinct() + .OrderBy(x => x) + .Select(x => UsingDirective(ParseName(x))) + .ToArray(); + + var mockLibrary = UsingDirective( + ParseName( + ( fakeItEasy is { } + ? fakeItEasy.ContainingNamespace + : substituteMetadata?.ContainingNamespace ) + ?.ToDisplayString() + ?? string.Empty + ) + ); + var unit = + CompilationUnit() + .AddUsings(mockLibrary) // TODO: [rlittlesii: March 01, 2024] toggle me + .AddUsings(UsingDirective(ParseName("System.Collections.ObjectModel"))) + .AddUsings(usings) + .AddMembers(namespaceDeclaration) + .NormalizeWhitespace(); + + productionContext.AddSource("AutoFixture", unit.ToFullString()); + } + } +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/BuilderExtensions.cs b/src/Testing.AutoFixtures/BuilderExtensions.cs new file mode 100644 index 0000000000..8f8350cf72 --- /dev/null +++ b/src/Testing.AutoFixtures/BuilderExtensions.cs @@ -0,0 +1,196 @@ +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +internal static class BuilderExtensions +{ + public const string Source = @"#nullable enable +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixture +{ + /// + /// for the extension methods. + /// + internal interface ITestFixtureBuilder {} + + /// + /// Default methods for the abstraction. + /// + internal static class TestFixtureBuilderExtensions + { + /// + /// Adds the specified field to the builder. + /// + /// The type of the builder. + /// The type of the field. + /// The this. + /// The field. + /// The value. + /// + // ReSharper disable once RedundantAssignment + public static TBuilder With(this TBuilder @this, ref TField field, TField value) + where TBuilder : ITestFixtureBuilder + { + field = value; + return @this; + } + + /// + /// Adds the specified list of fields to the builder. + /// + /// The type of the builder. + /// The type of the field. + /// The this. + /// The field. + /// The values. + /// + public static TBuilder With(this TBuilder @this, ref Collection? field, IEnumerable? values) + where TBuilder : ITestFixtureBuilder + { + if (values == null) + { + field = null; + } + else if (field != null) + { + foreach (var item in values) + field.Add(item); + } + + return @this; + } + + /// + /// Adds the specified list of fields to the builder. + /// + /// The type of the builder. + /// The type of the field. + /// The this. + /// The field. + /// The values. + /// + #pragma warning disable CA1002 + public static TBuilder With(this TBuilder @this, ref List? field, IEnumerable? values) + #pragma warning restore CA1002 + where TBuilder : ITestFixtureBuilder + { + if (values == null) + { + field = null; + } + else if (field is not null) + { + field.AddRange(values); + } + + return @this; + } + + /// + /// Adds the specified field to the builder. + /// + /// The type of the builder. + /// The type of the field. + /// The this. + /// The field. + /// The value. + /// + public static TBuilder With(this TBuilder @this, ref Collection? field, TField value) + where TBuilder : ITestFixtureBuilder + { + field?.Add(value); + return @this; + } + + /// + /// Adds the specified field to the builder. + /// + /// The type of the builder. + /// The type of the field. + /// The this. + /// The field. + /// The value. + /// + #pragma warning disable CA1002 + public static TBuilder With(this TBuilder @this, ref List? field, TField value) + #pragma warning restore CA1002 + where TBuilder : ITestFixtureBuilder + { + field?.Add(value); + return @this; + } + + /// + /// Adds the specified key value pair to the provided dictionary. + /// + /// The type of the builder. + /// The type of the key. + /// The type of the field. + /// The this. + /// The dictionary. + /// The key value pair. + /// + public static TBuilder With( + this TBuilder @this, ref Dictionary dictionary, KeyValuePair keyValuePair + ) + where TBuilder : ITestFixtureBuilder + where TKey : notnull + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + dictionary.Add(keyValuePair.Key, keyValuePair.Value); + return @this; + } + + /// + /// Adds the specified key and value to the provided dictionary. + /// + /// The type of the builder. + /// The type of the key. + /// The type of the field. + /// The this. + /// The dictionary. + /// The key. + /// The value. + /// + public static TBuilder With(this TBuilder @this, ref Dictionary dictionary, TKey key, TField value) + where TBuilder : ITestFixtureBuilder + where TKey : notnull + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + dictionary.Add(key, value); + return @this; + } + + /// + /// Adds the specified dictionary to the provided dictionary. + /// + /// The type of the builder. + /// The type of the key. + /// The type of the field. + /// The this. + /// The dictionary. + /// The key value pair. + /// + public static TBuilder With( + this TBuilder @this, + // ReSharper disable once RedundantAssignment + ref Dictionary dictionary, + Dictionary keyValuePair + ) + where TKey : notnull + { + dictionary = keyValuePair; + return @this; + } + } +}"; +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/BuilderInterface.cs b/src/Testing.AutoFixtures/BuilderInterface.cs new file mode 100644 index 0000000000..420bcb1615 --- /dev/null +++ b/src/Testing.AutoFixtures/BuilderInterface.cs @@ -0,0 +1,11 @@ +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +public static class BuilderInterface +{ + public const string Source = @" + /// + /// for the extension methods. + /// + internal interface ITestFixtureBuilder {} +"; +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/FixtureWithMethods.cs b/src/Testing.AutoFixtures/FixtureWithMethods.cs new file mode 100644 index 0000000000..52586f855a --- /dev/null +++ b/src/Testing.AutoFixtures/FixtureWithMethods.cs @@ -0,0 +1,1076 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +internal static class FixtureWithMethods +{ + public static MemberDeclarationSyntax[] BuildFixtureMethods(INamedTypeSymbol namedTypeSymbol) + { + return + [ + FieldMethod(namedTypeSymbol), + ]; + } + + public static MemberDeclarationSyntax FieldMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword) + ) + ) + .WithTypeParameterList( + TypeParameterList( + SingletonSeparatedList( + TypeParameter( + Identifier(FieldGenericParameter) + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + IdentifierName(FieldGenericParameter) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("value") + ) + .WithType( + IdentifierName(FieldGenericParameter) + ), + } + ) + ) + ) + .WithBody( + Block( + ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + IdentifierName("value") + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax CollectionEnumerableMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword) + ) + ) + .WithTypeParameterList( + TypeParameterList( + SingletonSeparatedList( + TypeParameter( + Identifier(FieldGenericParameter) + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + NullableType( + GenericName( + Identifier("Collection") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName(FieldGenericParameter) + ) + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("values") + ) + .WithType( + NullableType( + GenericName( + Identifier("IEnumerable") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName(FieldGenericParameter) + ) + ) + ) + ) + ), + } + ) + ) + ) + .WithBody( + Block( + IfStatement( + BinaryExpression( + SyntaxKind.EqualsExpression, + IdentifierName("values"), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ), + Block( + SingletonList( + ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ) + ) + ) + ) + ) + .WithElse( + ElseClause( + IfStatement( + BinaryExpression( + SyntaxKind.NotEqualsExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ), + Block( + SingletonList( + ForEachStatement( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.VarKeyword, + "var", + "var", + TriviaList() + ) + ), + Identifier("item"), + IdentifierName("values"), + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + IdentifierName("Add") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("item") + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax CollectionFieldMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + new[] + { + Token(SyntaxKind.PublicKeyword), + } + ) + ) + .WithTypeParameterList( + TypeParameterList( + SingletonSeparatedList( + TypeParameter( + Identifier("TField") + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + NullableType( + GenericName( + Identifier("Collection") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName("TField") + ) + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("value") + ) + .WithType( + IdentifierName("TField") + ), + } + ) + ) + ) + .WithBody( + Block( + ExpressionStatement( + ConditionalAccessExpression( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + InvocationExpression( + MemberBindingExpression( + IdentifierName("Add") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("value") + ) + ) + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax ListEnumerableMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + Token( + TriviaList( + Trivia( + PragmaWarningDirectiveTrivia( + Token(SyntaxKind.DisableKeyword), + true + ) + .WithErrorCodes( + SingletonSeparatedList( + IdentifierName("CA1002") + ) + ) + ) + ), + SyntaxKind.PublicKeyword, + TriviaList() + ) + ) + ) + .WithTypeParameterList( + TypeParameterList( + SingletonSeparatedList( + TypeParameter( + Identifier("TField") + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + NullableType( + GenericName( + Identifier("List") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName("TField") + ) + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("values") + ) + .WithType( + NullableType( + GenericName( + Identifier("IEnumerable") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName("TField") + ) + ) + ) + ) + ), + } + ) + ) + ) + .WithBody( + Block( + IfStatement( + BinaryExpression( + SyntaxKind.EqualsExpression, + IdentifierName("values"), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ), + Block( + SingletonList( + ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ) + ) + ) + ) + ) + .WithElse( + ElseClause( + IfStatement( + IsPatternExpression( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + UnaryPattern( + ConstantPattern( + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ) + ) + ), + Block( + SingletonList( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + IdentifierName("AddRange") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("values") + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + .WithOpenBraceToken( + Token( + TriviaList( + Trivia( + PragmaWarningDirectiveTrivia( + Token(SyntaxKind.RestoreKeyword), + true + ) + .WithErrorCodes( + SingletonSeparatedList( + IdentifierName("CA1002") + ) + ) + ) + ), + SyntaxKind.OpenBraceToken, + TriviaList() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax ListFieldMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + Token( + TriviaList( + Trivia( + PragmaWarningDirectiveTrivia( + Token(SyntaxKind.DisableKeyword), + true + ) + .WithErrorCodes( + SingletonSeparatedList( + IdentifierName("CA1002") + ) + ) + ) + ), + SyntaxKind.PublicKeyword, + TriviaList() + ) + ) + ) + .WithTypeParameterList( + TypeParameterList( + SingletonSeparatedList( + TypeParameter( + Identifier("TField") + ) + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + NullableType( + GenericName( + Identifier("List") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName("TField") + ) + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("value") + ) + .WithType( + IdentifierName("TField") + ), + } + ) + ) + ) + .WithBody( + Block( + ExpressionStatement( + ConditionalAccessExpression( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.FieldKeyword, + "field", + "field", + TriviaList() + ) + ), + InvocationExpression( + MemberBindingExpression( + IdentifierName("Add") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("value") + ) + ) + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + .WithOpenBraceToken( + Token( + TriviaList( + Trivia( + PragmaWarningDirectiveTrivia( + Token(SyntaxKind.RestoreKeyword), + true + ) + .WithErrorCodes( + SingletonSeparatedList( + IdentifierName("CA1002") + ) + ) + ) + ), + SyntaxKind.OpenBraceToken, + TriviaList() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax DictionaryDictionaryMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatement( + IdentifierName("AuthenticatorFixture"), + Identifier("With") + ) + .WithModifiers( + TokenList( + new[] + { + Token(SyntaxKind.PublicKeyword), + } + ) + ) + .WithTypeParameterList( + TypeParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + TypeParameter( + Identifier("TKey") + ), + Token(SyntaxKind.CommaToken), + TypeParameter( + Identifier("TField") + ), + } + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier("dictionary") + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + GenericName( + Identifier("Dictionary") + ) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + IdentifierName("TKey"), + Token(SyntaxKind.CommaToken), + IdentifierName("TField"), + } + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("keyValuePair") + ) + .WithType( + GenericName( + Identifier("KeyValuePair") + ) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + IdentifierName("TKey"), + Token(SyntaxKind.CommaToken), + IdentifierName("TField"), + } + ) + ) + ) + ), + } + ) + ) + ) + .WithConstraintClauses( + SingletonList( + TypeParameterConstraintClause( + IdentifierName("TKey") + ) + .WithConstraints( + SingletonSeparatedList( + TypeConstraint( + IdentifierName("notnull") + ) + ) + ) + ) + ) + .WithBody( + Block( + IfStatement( + BinaryExpression( + SyntaxKind.EqualsExpression, + IdentifierName("dictionary"), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ), + Block( + SingletonList( + ThrowStatement( + ObjectCreationExpression( + IdentifierName("ArgumentNullException") + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + InvocationExpression( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.NameOfKeyword, + "nameof", + "nameof", + TriviaList() + ) + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("dictionary") + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("dictionary"), + IdentifierName("Add") + ) + ) + .WithArgumentList( + ArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("keyValuePair"), + IdentifierName("Key") + ) + ), + Token(SyntaxKind.CommaToken), + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("keyValuePair"), + IdentifierName("Value") + ) + ), + } + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + ) + ); + } + + public static MemberDeclarationSyntax DictionaryKeyValuePairMethod(INamedTypeSymbol namedTypeSymbol) + { + return GlobalStatement( + LocalFunctionStatementSyntax(namedTypeSymbol) + .WithModifiers( + TokenList( + new[] + { + Token(SyntaxKind.PublicKeyword), + } + ) + ) + .WithTypeParameterList( + TypeParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + TypeParameter( + Identifier("TKey") + ), + Token(SyntaxKind.CommaToken), + TypeParameter( + Identifier("TField") + ), + } + ) + ) + ) + .WithParameterList( + ParameterList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Parameter( + Identifier("dictionary") + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.RefKeyword) + ) + ) + .WithType( + GenericName( + Identifier("Dictionary") + ) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + IdentifierName("TKey"), + Token(SyntaxKind.CommaToken), + IdentifierName("TField"), + } + ) + ) + ) + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("key") + ) + .WithType( + IdentifierName("TKey") + ), + Token(SyntaxKind.CommaToken), + Parameter( + Identifier("value") + ) + .WithType( + IdentifierName("TField") + ), + } + ) + ) + ) + .WithConstraintClauses( + SingletonList( + TypeParameterConstraintClause( + IdentifierName("TKey") + ) + .WithConstraints( + SingletonSeparatedList( + TypeConstraint( + IdentifierName("notnull") + ) + ) + ) + ) + ) + .WithBody( + Block( + IfStatement( + BinaryExpression( + SyntaxKind.EqualsExpression, + IdentifierName("dictionary"), + LiteralExpression( + SyntaxKind.NullLiteralExpression + ) + ), + Block( + SingletonList( + ThrowStatement( + ObjectCreationExpression( + IdentifierName("ArgumentNullException") + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + InvocationExpression( + IdentifierName( + Identifier( + TriviaList(), + SyntaxKind.NameOfKeyword, + "nameof", + "nameof", + TriviaList() + ) + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("dictionary") + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("dictionary"), + IdentifierName("Add") + ) + ) + .WithArgumentList( + ArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Argument( + IdentifierName("key") + ), + Token(SyntaxKind.CommaToken), + Argument( + IdentifierName("value") + ), + } + ) + ) + ) + ), + ReturnStatement( + ThisExpression() + ) + ) + ) + ); + } + + private static LocalFunctionStatementSyntax LocalFunctionStatementSyntax(ISymbol namedTypeSymbol) + { + return LocalFunctionStatement(IdentifierName($"{namedTypeSymbol.Name}Fixture"), Identifier(With)); + } + + private const string FieldGenericParameter = "TField"; + private const string With = "With"; +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/Methods.cs b/src/Testing.AutoFixtures/Methods.cs new file mode 100644 index 0000000000..1f78d24ae0 --- /dev/null +++ b/src/Testing.AutoFixtures/Methods.cs @@ -0,0 +1,117 @@ +using System.Text; +using Microsoft.CodeAnalysis; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures; + +public static class Methods +{ + public static string GetFullMetadataName(this ISymbol? s) + { + if (s == null || IsRootNamespace(s)) + { + return string.Empty; + } + + var sb = new StringBuilder(s.MetadataName); + var last = s; + + s = s.ContainingSymbol; + + while (!IsRootNamespace(s)) + { + if (s is ITypeSymbol && last is ITypeSymbol) + { + sb.Insert(0, '+'); + } + else + { + sb.Insert(0, '.'); + } + + sb.Insert(0, s.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); + //sb.Insert(0, s.MetadataName); + s = s.ContainingSymbol; + } + + return sb.ToString(); + + static bool IsRootNamespace(ISymbol symbol) + { + INamespaceSymbol? s; + return ( s = symbol as INamespaceSymbol ) != null && s.IsGlobalNamespace; + } + } + + public static bool IsOpenGenericType(this INamedTypeSymbol type) + { + return type.IsGenericType + && ( type.IsUnboundGenericType + || type.TypeArguments.All(z => z.TypeKind == TypeKind.TypeParameter) ); + } + + public static string GetGenericDisplayName(this ISymbol? symbol) + { + if (symbol == null || IsRootNamespace(symbol)) + { + return string.Empty; + } + + var sb = new StringBuilder(symbol.MetadataName); + if (symbol is INamedTypeSymbol namedTypeSymbol + && ( namedTypeSymbol.IsOpenGenericType() || namedTypeSymbol.IsGenericType )) + { + sb = new(symbol.Name); + if (namedTypeSymbol.IsOpenGenericType()) + { + sb.Append('<'); + for (var i = 1; i < namedTypeSymbol.Arity - 1; i++) + sb.Append(','); + sb.Append('>'); + } + else + { + sb.Append('<'); + for (var index = 0; index < namedTypeSymbol.TypeArguments.Length; index++) + { + var argument = namedTypeSymbol.TypeArguments[index]; + sb.Append(GetGenericDisplayName(argument)); + if (index < namedTypeSymbol.TypeArguments.Length - 1) + sb.Append(','); + } + + sb.Append('>'); + } + } + + var last = symbol; + + var workingSymbol = symbol.ContainingSymbol; + + while (!IsRootNamespace(workingSymbol)) + { + if (workingSymbol is ITypeSymbol && last is ITypeSymbol) + { + sb.Insert(0, '+'); + } + else + { + sb.Insert(0, '.'); + } + + sb.Insert( + 0, + workingSymbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat).Trim() + ); + //sb.Insert(0, symbol.MetadataName); + workingSymbol = workingSymbol.ContainingSymbol; + } + + return sb.ToString(); + + static bool IsRootNamespace(ISymbol symbol) + { + INamespaceSymbol? s; + return ( s = symbol as INamespaceSymbol ) != null && s.IsGlobalNamespace; + } + } +} \ No newline at end of file diff --git a/src/Testing.AutoFixtures/Properties/launchSettings.json b/src/Testing.AutoFixtures/Properties/launchSettings.json new file mode 100644 index 0000000000..265b82c2d0 --- /dev/null +++ b/src/Testing.AutoFixtures/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../Rocket.Surgery.Extensions.Testing.Fixtures.SourceGenerator.Sample/Rocket.Surgery.Extensions.Testing.Fixtures.SourceGenerator.Sample.csproj" + } + } +} diff --git a/src/Testing.AutoFixtures/PublicAPI.Shipped.txt b/src/Testing.AutoFixtures/PublicAPI.Shipped.txt new file mode 100644 index 0000000000..29699fde1f --- /dev/null +++ b/src/Testing.AutoFixtures/PublicAPI.Shipped.txt @@ -0,0 +1,10 @@ +#nullable enable +const Rocket.Surgery.Extensions.Testing.AutoFixtures.BuilderInterface.Source = "\n /// \n /// for the extension methods.\n /// \n internal interface ITestFixtureBuilder {}\n" -> string! +Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator +Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator.AutoFixtureGenerator() -> void +Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator.Initialize(Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext context) -> void +Rocket.Surgery.Extensions.Testing.AutoFixtures.BuilderInterface +Rocket.Surgery.Extensions.Testing.AutoFixtures.Methods +static Rocket.Surgery.Extensions.Testing.AutoFixtures.Methods.GetFullMetadataName(this Microsoft.CodeAnalysis.ISymbol? s) -> string! +static Rocket.Surgery.Extensions.Testing.AutoFixtures.Methods.GetGenericDisplayName(this Microsoft.CodeAnalysis.ISymbol? symbol) -> string! +static Rocket.Surgery.Extensions.Testing.AutoFixtures.Methods.IsOpenGenericType(this Microsoft.CodeAnalysis.INamedTypeSymbol! type) -> bool diff --git a/src/Testing.AutoFixtures/PublicAPI.Unshipped.txt b/src/Testing.AutoFixtures/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..815c92006a --- /dev/null +++ b/src/Testing.AutoFixtures/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/src/Testing.AutoFixtures/README.md b/src/Testing.AutoFixtures/README.md new file mode 100644 index 0000000000..890ede44a5 --- /dev/null +++ b/src/Testing.AutoFixtures/README.md @@ -0,0 +1,38 @@ +# Roslyn Source Generators Sample + +A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs. + +## How To? +### How to debug? +- Use the [launchSettings.json](Properties/launchSettings.json) profile. +- Debug tests. + +### How can I determine which syntax nodes I should expect? +Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/). + +### How to learn more about wiring source generators? +Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI) +The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md). + + +## Feature Requests + +1. AutoFixture on the class being fixtured. On the ViewModel not on a dummy class. +2. Where the files generate, file output path +3. Create the fixture in the same foldered/namespace where the class is decorated with the attribute +4. `[AutoMock]` public string Property { get; } +5. `[AutoMock]` decorate a class to use as the mock for an interface + 1. When you see the abstraction +6. Providing relevant mock values +7. Strict Fakes by default! Configurable. + + +## Todo + +Global Configuration `[global:FixtureConfig(path = “foo”, Suffix = “Fixture”)]` +- file name convention +- file path to generate the source, maybe a different assembly +- Builder method name convention (Not Until Requested) + +## MVP for Chase +- AutoFixture on the class, generate ClassNameFixture \ No newline at end of file diff --git a/src/Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.csproj b/src/Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.csproj new file mode 100644 index 0000000000..224ae03ff6 --- /dev/null +++ b/src/Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + latest + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/src/Testing.Fixtures/ITestFixtureBuilder.cs b/src/Testing.Fixtures/ITestFixtureBuilder.cs index d88a7f8fe7..2c699207d7 100644 --- a/src/Testing.Fixtures/ITestFixtureBuilder.cs +++ b/src/Testing.Fixtures/ITestFixtureBuilder.cs @@ -4,6 +4,4 @@ namespace Rocket.Surgery.Extensions.Testing.Fixtures; /// /// for the extension methods. /// -public interface ITestFixtureBuilder -{ -} +public interface ITestFixtureBuilder; \ No newline at end of file diff --git a/src/Testing.Fixtures/TestFixtureBuilderExtensions.cs b/src/Testing.Fixtures/TestFixtureBuilderExtensions.cs index fb2d09928c..8799fa0c93 100644 --- a/src/Testing.Fixtures/TestFixtureBuilderExtensions.cs +++ b/src/Testing.Fixtures/TestFixtureBuilderExtensions.cs @@ -58,19 +58,18 @@ public static TBuilder With(this TBuilder @this, ref Collectio /// The field. /// The values. /// -#pragma warning disable CA1002 + #pragma warning disable CA1002 public static TBuilder With(this TBuilder @this, ref List? field, IEnumerable? values) -#pragma warning restore CA1002 + #pragma warning restore CA1002 where TBuilder : ITestFixtureBuilder { if (values == null) { field = null; } - else if (field != null) + else if (field is { }) { - foreach (var item in values) - field.Add(item); + field.AddRange(values); } return @this; @@ -101,9 +100,9 @@ public static TBuilder With(this TBuilder @this, ref Collectio /// The field. /// The value. /// -#pragma warning disable CA1002 + #pragma warning disable CA1002 public static TBuilder With(this TBuilder @this, ref List? field, TField value) -#pragma warning restore CA1002 + #pragma warning restore CA1002 where TBuilder : ITestFixtureBuilder { field?.Add(value); @@ -121,10 +120,12 @@ public static TBuilder With(this TBuilder @this, ref ListThe key value pair. /// public static TBuilder With( - this TBuilder @this, ref Dictionary dictionary, KeyValuePair keyValuePair + this TBuilder @this, + ref Dictionary dictionary, + KeyValuePair keyValuePair ) - where TKey : notnull where TBuilder : ITestFixtureBuilder + where TKey : notnull { if (dictionary == null) { @@ -147,8 +148,8 @@ public static TBuilder With( /// The value. /// public static TBuilder With(this TBuilder @this, ref Dictionary dictionary, TKey key, TField value) - where TKey : notnull where TBuilder : ITestFixtureBuilder + where TKey : notnull { if (dictionary == null) { @@ -180,4 +181,4 @@ Dictionary keyValuePair dictionary = keyValuePair; return @this; } -} +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorData.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorData.cs new file mode 100644 index 0000000000..6311586fe3 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorData.cs @@ -0,0 +1,53 @@ +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests; + +public static class AutoFixtureGeneratorData +{ + public static IEnumerable Data => + new List + { + new object[] { BasicSource, }, + }; + + private const string BasicSource = @"using System; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Rocket.Surgery.Extensions.Testing.AutoFixture; + + +namespace Goony.Goo.Goo.Tests +{ + [AutoFixture(typeof(Authenticator))] + internal sealed partial class AuthenticatorFixture : ITestFixtureBuilder { } +} + +namespace Goony.Goo.Goo +{ + internal class Authenticator + { + public Authenticator(IAuthenticationClient authenticationClient, + ISecureStorage secureStorage, + ILogger logger) {} + } + internal interface ISecureStorage {} + internal interface IAuthenticationClient {} +} +"; + + private const string AttributeOnClassSource = @"using System; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Rocket.Surgery.Extensions.Testing.AutoFixture; + +namespace Goony.Goo.Goo.Tests +{ + [AutoFixture] + internal class Authenticator + { + public Authenticator(IAuthenticationClient authenticationClient, + ISecureStorage secureStorage, + ILogger logger) {} + } + internal interface ISecureStorage {} + internal interface IAuthenticationClient {} +}"; +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0#AutoFixture.verified.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0#AutoFixture.verified.cs new file mode 100644 index 0000000000..79f7517b33 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0#AutoFixture.verified.cs @@ -0,0 +1,26 @@ +//HintName: Rocket.Surgery.Extensions.Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator/AutoFixture.cs +using NSubstitute; +using System.Collections.ObjectModel; +using Goony.Goo.Goo; +using Microsoft.Extensions.Logging; + +namespace Goony.Goo.Goo.Tests +{ + internal sealed partial class AuthenticatorFixture : ITestFixtureBuilder + { + public static implicit operator Authenticator(AuthenticatorFixture fixture) => fixture.Build(); + public AuthenticatorFixture WithClient(Goony.Goo.Goo.IAuthenticationClient authenticationClient) => this.With(ref _authenticationClient, authenticationClient); + public AuthenticatorFixture WithStorage(Goony.Goo.Goo.ISecureStorage secureStorage) => this.With(ref _secureStorage, secureStorage); + public AuthenticatorFixture WithLogger(Microsoft.Extensions.Logging.ILogger logger) => this.With(ref _logger, logger); + public AuthenticatorFixture With(ref TField field, TField value) + { + field = value; + return this; + } + + private Authenticator Build() => new Authenticator(_authenticationClient, _secureStorage, _logger); + private Goony.Goo.Goo.IAuthenticationClient _authenticationClient = Substitute.For(); + private Goony.Goo.Goo.ISecureStorage _secureStorage = Substitute.For(); + private Microsoft.Extensions.Logging.ILogger _logger = Substitute.For>(); + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0.verified.txt b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0.verified.txt new file mode 100644 index 0000000000..46ccbd4f4d --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture_fe52e10a1e7987b0.verified.txt @@ -0,0 +1,20 @@ +{ + FinalDiagnostics: [], + GeneratorDiagnostics: { + Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator: [] + }, + ParseOptions: { + LanguageVersion: CSharp11, + DocumentationMode: Parse + }, + References: [ + Microsoft.Extensions.Logging.Abstractions.dll, + mscorlib.dll, + netstandard.dll, + NSubstitute.dll, + System.Core.dll, + System.dll, + System.Private.CoreLib.dll, + System.Runtime.dll + ] +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute#AutoFixtureAttribute.verified.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute#AutoFixtureAttribute.verified.cs new file mode 100644 index 0000000000..48daeaaf05 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute#AutoFixtureAttribute.verified.cs @@ -0,0 +1,15 @@ +//HintName: Rocket.Surgery.Extensions.Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator/AutoFixtureAttribute.cs +using System; +using System.Diagnostics; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixture +{ + [AttributeUsage(AttributeTargets.Class)] + [Conditional("CODEGEN")] + internal class AutoFixtureAttribute : Attribute + { + public AutoFixtureAttribute(Type type) => Type = type; + + public Type Type { get; } + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute.verified.txt b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute.verified.txt new file mode 100644 index 0000000000..6388cb62bf --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute.verified.txt @@ -0,0 +1,19 @@ +{ + FinalDiagnostics: [], + GeneratorDiagnostics: { + Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator: [] + }, + ParseOptions: { + LanguageVersion: CSharp11, + DocumentationMode: Parse + }, + References: [ + mscorlib.dll, + netstandard.dll, + System.Core.dll, + System.dll, + System.Private.CoreLib.dll, + System.Private.CoreLib.dll, + System.Runtime.dll + ] +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions#BuilderInterface.verified.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions#BuilderInterface.verified.cs new file mode 100644 index 0000000000..b3bef18454 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions#BuilderInterface.verified.cs @@ -0,0 +1,6 @@ +//HintName: Rocket.Surgery.Extensions.Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator/BuilderInterface.cs + + /// + /// for the extension methods. + /// + internal interface ITestFixtureBuilder {} diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions.verified.txt b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions.verified.txt new file mode 100644 index 0000000000..6388cb62bf --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions.verified.txt @@ -0,0 +1,19 @@ +{ + FinalDiagnostics: [], + GeneratorDiagnostics: { + Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator: [] + }, + ParseOptions: { + LanguageVersion: CSharp11, + DocumentationMode: Parse + }, + References: [ + mscorlib.dll, + netstandard.dll, + System.Core.dll, + System.dll, + System.Private.CoreLib.dll, + System.Private.CoreLib.dll, + System.Runtime.dll + ] +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs new file mode 100644 index 0000000000..818d5c99d7 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs @@ -0,0 +1,26 @@ +//HintName: Rocket.Surgery.Extensions.Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator/AutoFixture.cs +using FakeItEasy; +using System.Collections.ObjectModel; +using Goony.Goo.Goo; +using Microsoft.Extensions.Logging; + +namespace Goony.Goo.Goo.Tests +{ + internal sealed partial class AuthenticatorFixture : ITestFixtureBuilder + { + public static implicit operator Authenticator(AuthenticatorFixture fixture) => fixture.Build(); + public AuthenticatorFixture WithClient(Goony.Goo.Goo.IAuthenticationClient authenticationClient) => this.With(ref _authenticationClient, authenticationClient); + public AuthenticatorFixture WithStorage(Goony.Goo.Goo.ISecureStorage secureStorage) => this.With(ref _secureStorage, secureStorage); + public AuthenticatorFixture WithLogger(Microsoft.Extensions.Logging.ILogger logger) => this.With(ref _logger, logger); + public AuthenticatorFixture With(ref TField field, TField value) + { + field = value; + return this; + } + + private Authenticator Build() => new Authenticator(_authenticationClient, _secureStorage, _logger); + private Goony.Goo.Goo.IAuthenticationClient _authenticationClient = A.Fake(); + private Goony.Goo.Goo.ISecureStorage _secureStorage = A.Fake(); + private Microsoft.Extensions.Logging.ILogger _logger = A.Fake>(); + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt new file mode 100644 index 0000000000..d2442843d1 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt @@ -0,0 +1,20 @@ +{ + FinalDiagnostics: [], + GeneratorDiagnostics: { + Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator: [] + }, + ParseOptions: { + LanguageVersion: CSharp11, + DocumentationMode: Parse + }, + References: [ + FakeItEasy.dll, + Microsoft.Extensions.Logging.Abstractions.dll, + mscorlib.dll, + netstandard.dll, + System.Core.dll, + System.dll, + System.Private.CoreLib.dll, + System.Runtime.dll + ] +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs new file mode 100644 index 0000000000..79f7517b33 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0#AutoFixture.verified.cs @@ -0,0 +1,26 @@ +//HintName: Rocket.Surgery.Extensions.Testing.AutoFixtures/Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator/AutoFixture.cs +using NSubstitute; +using System.Collections.ObjectModel; +using Goony.Goo.Goo; +using Microsoft.Extensions.Logging; + +namespace Goony.Goo.Goo.Tests +{ + internal sealed partial class AuthenticatorFixture : ITestFixtureBuilder + { + public static implicit operator Authenticator(AuthenticatorFixture fixture) => fixture.Build(); + public AuthenticatorFixture WithClient(Goony.Goo.Goo.IAuthenticationClient authenticationClient) => this.With(ref _authenticationClient, authenticationClient); + public AuthenticatorFixture WithStorage(Goony.Goo.Goo.ISecureStorage secureStorage) => this.With(ref _secureStorage, secureStorage); + public AuthenticatorFixture WithLogger(Microsoft.Extensions.Logging.ILogger logger) => this.With(ref _logger, logger); + public AuthenticatorFixture With(ref TField field, TField value) + { + field = value; + return this; + } + + private Authenticator Build() => new Authenticator(_authenticationClient, _secureStorage, _logger); + private Goony.Goo.Goo.IAuthenticationClient _authenticationClient = Substitute.For(); + private Goony.Goo.Goo.ISecureStorage _secureStorage = Substitute.For(); + private Microsoft.Extensions.Logging.ILogger _logger = Substitute.For>(); + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt new file mode 100644 index 0000000000..46ccbd4f4d --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes_fe52e10a1e7987b0.verified.txt @@ -0,0 +1,20 @@ +{ + FinalDiagnostics: [], + GeneratorDiagnostics: { + Rocket.Surgery.Extensions.Testing.AutoFixtures.AutoFixtureGenerator: [] + }, + ParseOptions: { + LanguageVersion: CSharp11, + DocumentationMode: Parse + }, + References: [ + Microsoft.Extensions.Logging.Abstractions.dll, + mscorlib.dll, + netstandard.dll, + NSubstitute.dll, + System.Core.dll, + System.dll, + System.Private.CoreLib.dll, + System.Runtime.dll + ] +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.cs b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.cs new file mode 100644 index 0000000000..69dcaffac9 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/AutoFixtureGeneratorTests.cs @@ -0,0 +1,158 @@ +using FakeItEasy; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Rocket.Surgery.Extensions.Testing.SourceGenerators; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests; + +public class AutoFixtureGeneratorTests +{ + [Fact] + public async Task GivenAutoFixture_WhenGenerate_ThenShouldGenerateAutoFixtureAttribute() + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(List<>)) + .IgnoreOutputFile("BuilderInterface.cs") + .Build(); + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result); + } + + [Fact] + public async Task GivenAutoFixture_WhenGenerate_ThenShouldGenerateFixtureBuilderExtensions() + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(List<>)) + .IgnoreOutputFile("AutoFixtureAttribute.cs") + .Build(); + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result); + } + + [Theory] + [MemberData(nameof(AutoFixtureGeneratorData.Data), MemberType = typeof(AutoFixtureGeneratorData))] + public async Task GivenAutoFixtureAttributeUsage_WhenGenerate_ThenGeneratedAutoFixture(string source) + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(ILogger<>)) + .AddReferences(typeof(Substitute)) + .IgnoreOutputFile("BuilderInterface.cs") + .IgnoreOutputFile("AutoFixtureAttribute.cs") + .AddSources(source) + .Build(); + + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result).UseHashedParameters(source); + } + + [Theory] + [MemberData(nameof(AutoFixtureGeneratorData.Data), MemberType = typeof(AutoFixtureGeneratorData))] + public async Task GivenFakeItEasy_WhenGenerate_ThenGeneratedAutoFixtureWithFakes(string source) + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(ILogger<>)) + .AddReferences(typeof(Fake)) + .IgnoreOutputFile("BuilderInterface.cs") + .IgnoreOutputFile("AutoFixtureAttribute.cs") + .AddSources(source) + .Build(); + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result).UseHashedParameters(source); + } + + [Theory] + [MemberData(nameof(AutoFixtureGeneratorData.Data), MemberType = typeof(AutoFixtureGeneratorData))] + public async Task GivenNSubstitute_WhenGenerate_ThenGeneratedAutoFixtureWithFakes(string source) + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(ILogger<>)) + .AddReferences(typeof(Substitute)) + .IgnoreOutputFile("BuilderInterface.cs") + .IgnoreOutputFile("AutoFixtureAttribute.cs") + .AddSources(source) + .Build(); + + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result).UseHashedParameters(source); + } + +// [Fact] + public async Task GivenAttributeOnClass_When_ThenShouldGenerateAutoFixture() + { + // Given + var generatorInstance = + GeneratorTestContextBuilder + .Create() + .WithGenerator() + .AddReferences(typeof(ILogger<>)) + .IgnoreOutputFile("BuilderExtensions.cs") + .IgnoreOutputFile("Attribute.cs") + .AddSources( + @"using System; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Rocket.Surgery.Extensions.Testing.AutoFixture; + +namespace Goony.Goo.Goo.Tests +{ + [AutoFixture] + internal class Authenticator + { + public Authenticator(IAuthenticationClient authenticationClient, + ISecureStorage secureStorage, + ILogger logger) {} + } + internal interface ISecureStorage {} + internal interface IAuthenticationClient {} +}" + ) + .Build(); + + + // When + var result = await generatorInstance.GenerateAsync(); + + // Then + await Verify(result); + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/ModuleInitializer.cs b/test/Testing.AutoFixtures.Tests/ModuleInitializer.cs new file mode 100644 index 0000000000..115561fe5c --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/ModuleInitializer.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using DiffEngine; +using Rocket.Surgery.Extensions.Testing.SourceGenerators; + +namespace Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests; + +internal class ModuleInitializer +{ + [ModuleInitializer] + public static void Initialize() + { + VerifyGeneratorTextContext.Initialize(); + DiffRunner.Disabled = true; + } +} \ No newline at end of file diff --git a/test/Testing.AutoFixtures.Tests/Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests.csproj b/test/Testing.AutoFixtures.Tests/Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests.csproj new file mode 100644 index 0000000000..15b4835800 --- /dev/null +++ b/test/Testing.AutoFixtures.Tests/Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + false + Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests + Rocket.Surgery.Extensions.Testing.AutoFixtures.Tests + + + + + + + + + + + + + + + + + + diff --git a/test/Testing.Fixtures.Tests/TestFixtureBuilderExtensionTests.cs b/test/Testing.Fixtures.Tests/TestFixtureBuilderExtensionTests.cs index 1d349b6c13..5709dfc980 100644 --- a/test/Testing.Fixtures.Tests/TestFixtureBuilderExtensionTests.cs +++ b/test/Testing.Fixtures.Tests/TestFixtureBuilderExtensionTests.cs @@ -1,27 +1,7 @@ -using Xunit; - namespace Rocket.Surgery.Extensions.Testing.Fixtures.Tests; public sealed class TestFixtureBuilderExtensionTests { - public static IEnumerable Data => - new List - { - new object[] { "testing", string.Empty, string.Empty }, - new object[] { "testing", "testing", string.Empty }, - new object[] { "testing", "testing", "one" }, - new object[] { "testing", "one", "two" } - }; - - public static IEnumerable KeyValuePairs => - new List - { - new object[] { "testing", string.Empty }, - new object[] { "testing", "one" }, - new object[] { "testing", "two" }, - new object[] { "testing", "one two" } - }; - [Fact] public void Should_Add_Dictionary() { @@ -29,7 +9,7 @@ public void Should_Add_Dictionary() var dictionary = new Dictionary { { "check", "one" }, - { "testing", "two" } + { "testing", "two" }, }; TestFixture builder = new TestFixtureBuilder() @@ -39,6 +19,34 @@ public void Should_Add_Dictionary() Assert.Equal(dictionary, builder.Variables); } + [Fact] + public void Should_Add_To_List() + { + // Given, When + TestFixture builder = new TestFixtureBuilder().WithTest("testing"); + + // Then + Assert.Equal(new[] { "testing", }, builder.Tests); + } + + public static IEnumerable Data => + new List + { + new object[] { "testing", string.Empty, string.Empty, }, + new object[] { "testing", "testing", string.Empty, }, + new object[] { "testing", "testing", "one", }, + new object[] { "testing", "one", "two", }, + }; + + public static IEnumerable KeyValuePairs => + new List + { + new object[] { "testing", string.Empty, }, + new object[] { "testing", "one", }, + new object[] { "testing", "two", }, + new object[] { "testing", "one two", }, + }; + [Theory] [MemberData(nameof(KeyValuePairs))] public void Should_Add_Key_Value(string key, string value) @@ -55,7 +63,7 @@ public void Should_Add_Key_Value(string key, string value) public void Should_Add_Key_Value_Pair(string key, string value) { // Given, When - TestFixture builder = new TestFixtureBuilder().WithKeyValue(new KeyValuePair(key, value)); + TestFixture builder = new TestFixtureBuilder().WithKeyValue(new(key, value)); // Then Assert.Equal(value, builder.Variables[key]); @@ -66,20 +74,10 @@ public void Should_Add_Key_Value_Pair(string key, string value) public void Should_Add_Range(string test1, string test2, string test3) { // Given, When - TestFixture builder = new TestFixtureBuilder().WithTests(new[] { test1, test2, test3 }); - - // Then - Assert.Equal(new[] { test1, test2, test3 }, builder.Tests); - } - - [Fact] - public void Should_Add_To_List() - { - // Given, When - TestFixture builder = new TestFixtureBuilder().WithTest("testing"); + TestFixture builder = new TestFixtureBuilder().WithTests(new[] { test1, test2, test3, }); // Then - Assert.Equal(new[] { "testing" }, builder.Tests); + Assert.Equal(new[] { test1, test2, test3, }, builder.Tests); } [Theory] @@ -110,4 +108,4 @@ public void Should_Return_Name(string name) // Then Assert.Equal(name, builder.Name); } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/Fake/AutoFakeEnumerableTests.cs b/test/Testing.Tests/Fake/AutoFakeEnumerableTests.cs index a8aa0ce227..e8c654e49d 100644 --- a/test/Testing.Tests/Fake/AutoFakeEnumerableTests.cs +++ b/test/Testing.Tests/Fake/AutoFakeEnumerableTests.cs @@ -1,17 +1,11 @@ using FluentAssertions; using Microsoft.Extensions.Logging; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Fake; public class AutoFakeEnumerableTests : AutoFakeTest { - public AutoFakeEnumerableTests(ITestOutputHelper outputHelper) : base(outputHelper, LogLevel.Information) - { - } - - [Fact] public void Does_Not_Auto_Fake_Enumerable() { @@ -83,25 +77,21 @@ public void Handle_Four_Fake_Item() [Obsolete("TBD")] public void Should_Handle_Creating_A_Mock_With_Logger() { - Action a = () => - { - var lt = AutoFake.Resolve(); - AutoFake.Provide(lt); - }; + var a = () => + { + var lt = AutoFake.Resolve(); + AutoFake.Provide(lt); + }; a.Should().NotThrow(); } - public interface Item - { - } + public AutoFakeEnumerableTests(ITestOutputHelper outputHelper) : base(outputHelper, LogLevel.Information) { } - private class A : Item - { - } + public interface Item { } - private class B : Item - { - } + private class A : Item { } + + private class B : Item { } private class LoggerTest : Item { @@ -113,4 +103,4 @@ public LoggerTest(ILogger logger) } } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/Fake/AutoFakePopulateTests.cs b/test/Testing.Tests/Fake/AutoFakePopulateTests.cs index f8296ca5f9..f4a6e6d387 100644 --- a/test/Testing.Tests/Fake/AutoFakePopulateTests.cs +++ b/test/Testing.Tests/Fake/AutoFakePopulateTests.cs @@ -3,22 +3,17 @@ using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Fake; public class AutoFakePopulateTests : AutoFakeTest { - public AutoFakePopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - [Fact] public void Should_Populate_Configuration_And_Services() { Container.Populate(new ServiceCollection().AddSingleton(new A())); - Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()); + Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); } @@ -29,7 +24,7 @@ public void Should_Populate_Services() Populate( new ServiceCollection() .AddSingleton(new A()) - .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()) + .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()) ); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); @@ -43,7 +38,7 @@ public void Should_Populate_Container() Container.IsRegistered().Should().BeFalse(); } - private class A - { - } -} + public AutoFakePopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class A { } +} \ No newline at end of file diff --git a/test/Testing.Tests/Fake/AutoFakeTestTests.cs b/test/Testing.Tests/Fake/AutoFakeTestTests.cs index f58af239b6..1a34ae417a 100644 --- a/test/Testing.Tests/Fake/AutoFakeTestTests.cs +++ b/test/Testing.Tests/Fake/AutoFakeTestTests.cs @@ -3,41 +3,12 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Fake; public class AutoFakeTestTests : AutoFakeTest { - public AutoFakeTestTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - private class Impl : AutoFakeTest - { - public Impl(ITestOutputHelper outputHelper) : base(outputHelper) - { - Logger.LogError("abcd"); - Logger.LogError("abcd {Something}", "somevalue"); - } - } - - private class DoubleAccess : AutoFakeTest - { - public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - protected override IContainer BuildContainer(IContainer container) - { - // invalid do not touch ServiceProvider or Container while constructing the container.... - return Container.GetRequiredService(); - } - - public IContainer Self => Container; - } - [Fact] public void Should_Create_Usable_Logger() { @@ -45,19 +16,6 @@ public void Should_Create_Usable_Logger() A.CallTo(() => AutoFake.Resolve().WriteLine(A._)).MustHaveHappened(); } - private class LoggerImpl : AutoFakeTest - { - public LoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - public void Write() - { - AutoFake.Resolve().LogError("abcd"); - AutoFake.Resolve().LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_Logger() { @@ -66,19 +24,6 @@ public void Should_Inject_Logger() A.CallTo(() => AutoFake.Resolve().WriteLine(A._)).MustHaveHappened(); } - private class LoggerFactoryImpl : AutoFakeTest - { - public LoggerFactoryImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - public void Write() - { - AutoFake.Resolve().CreateLogger("").LogError("abcd"); - AutoFake.Resolve().CreateLogger("").LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_LoggerFactory() { @@ -87,19 +32,6 @@ public void Should_Inject_LoggerFactory() A.CallTo(() => AutoFake.Resolve().WriteLine(A._)).MustHaveHappened(); } - public class GenericLoggerImpl : AutoFakeTest - { - public GenericLoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - public void Write() - { - AutoFake.Resolve>().LogError("abcd"); - AutoFake.Resolve>().LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_GenericLogger() { @@ -131,8 +63,11 @@ public void Should_Not_Fake_Optional_Parameters() public void Should_Populate_Optional_Parameters_When_Provided() { AutoFake.Provide(new MyItem()); - AutoFake.Resolve().Item.Should().NotBeNull() - .And.Match(z => !FakeItEasy.Fake.IsFake(z)); + AutoFake + .Resolve() + .Item.Should() + .NotBeNull() + .And.Match(z => !FakeItEasy.Fake.IsFake(z)); } [Fact] @@ -143,21 +78,74 @@ public void Should_Fail_If_Container_Is_Touched_When_Building() a.Should().Throw(); } - private class MyItem : IItem + public AutoFakeTestTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class Impl : AutoFakeTest { + public Impl(ITestOutputHelper outputHelper) : base(outputHelper) + { + Logger.LogError("abcd"); + Logger.LogError("abcd {Something}", "somevalue"); + } } - public interface IItem + private class DoubleAccess : AutoFakeTest { + public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public IContainer Self => Container; + + protected override IContainer BuildContainer(IContainer container) + { + // invalid do not touch ServiceProvider or Container while constructing the container.... + return Container.GetRequiredService(); + } } - private class Optional + private class LoggerImpl : AutoFakeTest { - public IItem? Item { get; } + public LoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) { } + public void Write() + { + AutoFake.Resolve().LogError("abcd"); + AutoFake.Resolve().LogError("abcd {Something}", "somevalue"); + } + } + + private class LoggerFactoryImpl : AutoFakeTest + { + public LoggerFactoryImpl(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public void Write() + { + AutoFake.Resolve().CreateLogger("").LogError("abcd"); + AutoFake.Resolve().CreateLogger("").LogError("abcd {Something}", "somevalue"); + } + } + + public class GenericLoggerImpl : AutoFakeTest + { + public GenericLoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public void Write() + { + AutoFake.Resolve>().LogError("abcd"); + AutoFake.Resolve>().LogError("abcd {Something}", "somevalue"); + } + } + + private class MyItem : IItem { } + + public interface IItem { } + + private class Optional + { public Optional(IItem? item = null) { Item = item; } + + public IItem? Item { get; } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/LoggerTestTests.cs b/test/Testing.Tests/LoggerTestTests.cs index 73d1a0ca96..37671795b3 100644 --- a/test/Testing.Tests/LoggerTestTests.cs +++ b/test/Testing.Tests/LoggerTestTests.cs @@ -2,26 +2,12 @@ using FluentAssertions; using Microsoft.Extensions.Logging; using Serilog.Events; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests; public class LoggerTestTests : LoggerTest { - public LoggerTestTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - private class Impl : LoggerTest - { - public Impl(ITestOutputHelper outputHelper) : base(outputHelper) - { - Logger.LogError("abcd"); - Logger.LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Create_Usable_Logger() { @@ -96,26 +82,6 @@ public void Should_Create_A_Log_Serilog_Stream() logs.Should().HaveCount(3); } - [Theory] - [ClassData(typeof(LoggerTheoryCollection))] - public void Should_Support_Theory_Data(IEnumerable messages, int count) - { - using var listener = CaptureLogs(out var logs); - foreach (var item in messages) - Logger.LogInformation(item); - logs.Should().HaveCount(count); - } - - public class LoggerTheoryCollection : TheoryCollection<(IEnumerable, int)> - { - protected override IEnumerable<(IEnumerable, int)> GetData() - { - yield return ( new[] { "1", "2", "3" }, 3 ); - yield return ( new[] { "1", "2" }, 2 ); - yield return ( new[] { "1" }, 1 ); - } - } - [Fact] public void Should_Exclude_SourceContext_Messages() { @@ -149,4 +115,35 @@ public void Should_Include_SourceContext_Messages() logs.Should().HaveCount(3); } -} + + public LoggerTestTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class Impl : LoggerTest + { + public Impl(ITestOutputHelper outputHelper) : base(outputHelper) + { + Logger.LogError("abcd"); + Logger.LogError("abcd {Something}", "somevalue"); + } + } + + [Theory] + [ClassData(typeof(LoggerTheoryCollection))] + public void Should_Support_Theory_Data(IEnumerable messages, int count) + { + using var listener = CaptureLogs(out var logs); + foreach (var item in messages) + Logger.LogInformation(item); + logs.Should().HaveCount(count); + } + + public class LoggerTheoryCollection : TheoryCollection<(IEnumerable, int)> + { + protected override IEnumerable<(IEnumerable, int)> GetData() + { + yield return ( new[] { "1", "2", "3", }, 3 ); + yield return ( new[] { "1", "2", }, 2 ); + yield return ( new[] { "1", }, 1 ); + } + } +} \ No newline at end of file diff --git a/test/Testing.Tests/Mock/AutoMockEnumerableTests.cs b/test/Testing.Tests/Mock/AutoMockEnumerableTests.cs index ccabfb6e92..aec33b3c57 100644 --- a/test/Testing.Tests/Mock/AutoMockEnumerableTests.cs +++ b/test/Testing.Tests/Mock/AutoMockEnumerableTests.cs @@ -1,16 +1,11 @@ using FluentAssertions; using Microsoft.Extensions.Logging; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Mock; public class AutoMockEnumerableTests : AutoMockTest { - public AutoMockEnumerableTests(ITestOutputHelper outputHelper) : base(outputHelper, LogLevel.Information) - { - } - [Fact] public void Does_Not_Auto_Fake_Enumerable() { @@ -23,11 +18,11 @@ public void Does_Not_Auto_Fake_Enumerable() [Fact] public void Should_Handle_Creating_A_Mock_With_Logger() { - Action a = () => - { - var lt = AutoMock.Resolve(); - AutoMock.Provide(lt); - }; + var a = () => + { + var lt = AutoMock.Resolve(); + AutoMock.Provide(lt); + }; a.Should().NotThrow(); } @@ -89,17 +84,13 @@ public void Handle_Four_Fake_Item() result.Should().Contain(fake4); } - public interface Item - { - } + public AutoMockEnumerableTests(ITestOutputHelper outputHelper) : base(outputHelper, LogLevel.Information) { } - private class A : Item - { - } + public interface Item { } - private class B : Item - { - } + private class A : Item { } + + private class B : Item { } private class LoggerTest : Item { @@ -111,4 +102,4 @@ public LoggerTest(ILogger logger) } } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/Mock/AutoMockPopulateTests.cs b/test/Testing.Tests/Mock/AutoMockPopulateTests.cs index 2296f6d028..edfa35df79 100644 --- a/test/Testing.Tests/Mock/AutoMockPopulateTests.cs +++ b/test/Testing.Tests/Mock/AutoMockPopulateTests.cs @@ -3,22 +3,17 @@ using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Mock; public class AutoMockPopulateTests : AutoMockTest { - public AutoMockPopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - [Fact] public void Should_Populate_Configuration_And_Services() { Container.Populate(new ServiceCollection().AddSingleton(new A())); - Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()); + Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); } @@ -29,7 +24,7 @@ public void Should_Populate_Services() Populate( new ServiceCollection() .AddSingleton(new A()) - .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()) + .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()) ); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); @@ -43,7 +38,7 @@ public void Should_Populate_Container() Container.IsRegistered().Should().BeFalse(); } - private class A - { - } -} + public AutoMockPopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class A { } +} \ No newline at end of file diff --git a/test/Testing.Tests/Mock/AutoMockTestTests.cs b/test/Testing.Tests/Mock/AutoMockTestTests.cs index 8349d2d80a..a842e2a10b 100644 --- a/test/Testing.Tests/Mock/AutoMockTestTests.cs +++ b/test/Testing.Tests/Mock/AutoMockTestTests.cs @@ -3,41 +3,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Mock; public class AutoMockTestTests : AutoMockTest { - public AutoMockTestTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - private class Impl : AutoMockTest - { - public Impl(ITestOutputHelper outputHelper) : base(outputHelper) - { - Logger.LogError("abcd"); - Logger.LogError("abcd {Something}", "somevalue"); - } - } - - private class DoubleAccess : AutoMockTest - { - public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - protected override IContainer BuildContainer(IContainer container) - { - // invalid do not touch ServiceProvider or Container while constructing the container.... - return Container.GetRequiredService(); - } - - public IContainer Self => Container; - } - [Fact] public void Should_Create_Usable_Logger() { @@ -82,21 +53,41 @@ public void Should_Fail_If_Container_Is_Touched_When_Building() a.Should().Throw(); } - private class MyItem : IItem + public AutoMockTestTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class Impl : AutoMockTest { + public Impl(ITestOutputHelper outputHelper) : base(outputHelper) + { + Logger.LogError("abcd"); + Logger.LogError("abcd {Something}", "somevalue"); + } } - public interface IItem + private class DoubleAccess : AutoMockTest { + public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public IContainer Self => Container; + + protected override IContainer BuildContainer(IContainer container) + { + // invalid do not touch ServiceProvider or Container while constructing the container.... + return Container.GetRequiredService(); + } } + private class MyItem : IItem { } + + public interface IItem { } + private class Optional { - public IItem? Item { get; } - public Optional(IItem? item = null) { Item = item; } + + public IItem? Item { get; } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/Substitute/AutoSubstituteEnumerableTests.cs b/test/Testing.Tests/Substitute/AutoSubstituteEnumerableTests.cs index 1a23325a05..8eb3667456 100644 --- a/test/Testing.Tests/Substitute/AutoSubstituteEnumerableTests.cs +++ b/test/Testing.Tests/Substitute/AutoSubstituteEnumerableTests.cs @@ -1,17 +1,11 @@ using FluentAssertions; using Microsoft.Extensions.Logging; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Substitute; public class AutoSubstituteEnumerableTests : AutoSubstituteTest { - public AutoSubstituteEnumerableTests(ITestOutputHelper testOutputHelper) - : base(testOutputHelper, LogLevel.Information) - { - } - [Fact] public void Does_Not_Auto_Substitute_Enumerable() { @@ -83,25 +77,22 @@ public void Handle_Four_Substitute_Item() [Obsolete("TBD")] public void Should_Handle_Creating_A_Substitute_With_Logger() { - Action a = () => - { - var lt = AutoSubstitute.Resolve(); - AutoSubstitute.Provide(lt); - }; + var a = () => + { + var lt = AutoSubstitute.Resolve(); + AutoSubstitute.Provide(lt); + }; a.Should().NotThrow(); } - public interface Item - { - } + public AutoSubstituteEnumerableTests(ITestOutputHelper testOutputHelper) + : base(testOutputHelper, LogLevel.Information) { } - private class A : Item - { - } + public interface Item { } - private class B : Item - { - } + private class A : Item { } + + private class B : Item { } private class LoggerTest : Item { @@ -113,4 +104,4 @@ public LoggerTest(ILogger logger) } } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/Substitute/AutoSubstitutePopulateTests.cs b/test/Testing.Tests/Substitute/AutoSubstitutePopulateTests.cs index 0dab2901e1..6aad4ce4e3 100644 --- a/test/Testing.Tests/Substitute/AutoSubstitutePopulateTests.cs +++ b/test/Testing.Tests/Substitute/AutoSubstitutePopulateTests.cs @@ -3,22 +3,17 @@ using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests.Substitute; public class AutoSubstitutePopulateTests : AutoSubstituteTest { - public AutoSubstitutePopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - [Fact] public void Should_Populate_Configuration_And_Services() { Container.Populate(new ServiceCollection().AddSingleton(new A())); - Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()); + Container.RegisterInstance(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); } @@ -29,7 +24,7 @@ public void Should_Populate_Services() Populate( new ServiceCollection() .AddSingleton(new A()) - .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1" }).Build()) + .AddSingleton(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["a"] = "1", }).Build()) ); Configuration.GetValue("a").Should().Be("1"); ServiceProvider.GetRequiredService().Should().BeSameAs(ServiceProvider.GetService()); @@ -43,7 +38,7 @@ public void Should_Populate_Container() Container.IsRegistered().Should().BeFalse(); } - private class A - { - } -} + public AutoSubstitutePopulateTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class A { } +} \ No newline at end of file diff --git a/test/Testing.Tests/Substitute/AutoSubstituteTestTests.cs b/test/Testing.Tests/Substitute/AutoSubstituteTestTests.cs index 8bae2e0d63..0e9807d4fb 100644 --- a/test/Testing.Tests/Substitute/AutoSubstituteTestTests.cs +++ b/test/Testing.Tests/Substitute/AutoSubstituteTestTests.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NSubstitute; -using Xunit; using Xunit.Abstractions; using Arg = NSubstitute.Arg; @@ -11,34 +10,6 @@ namespace Rocket.Surgery.Extensions.Testing.Tests.Substitute; public class AutoSubstituteTestTests : AutoSubstituteTest { - public AutoSubstituteTestTests(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - private class Impl : AutoSubstituteTest - { - public Impl(ITestOutputHelper outputHelper) : base(outputHelper) - { - Logger.LogError("abcd"); - Logger.LogError("abcd {Something}", "somevalue"); - } - } - - private class DoubleAccess : AutoSubstituteTest - { - public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - protected override IContainer BuildContainer(IContainer container) - { - // invalid do not touch ServiceProvider or Container while constructing the container.... - return Container.GetRequiredService(); - } - - public IContainer Self => Container; - } - [Fact] public void Should_Create_Usable_Logger() { @@ -46,19 +17,6 @@ public void Should_Create_Usable_Logger() AutoSubstitute.Resolve().Received().WriteLine(Arg.Any()); } - private class LoggerImpl : AutoSubstituteTest - { - public LoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - public void Write() - { - AutoSubstitute.Resolve().LogError("abcd"); - AutoSubstitute.Resolve().LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_Logger() { @@ -67,19 +25,6 @@ public void Should_Inject_Logger() AutoSubstitute.Resolve().Received().WriteLine(Arg.Any()); } - private class LoggerFactoryImpl : AutoSubstituteTest - { - public LoggerFactoryImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - } - - public void Write() - { - AutoSubstitute.Resolve().CreateLogger("").LogError("abcd"); - AutoSubstitute.Resolve().CreateLogger("").LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_LoggerFactory() { @@ -88,22 +33,6 @@ public void Should_Inject_LoggerFactory() AutoSubstitute.Resolve().Received().WriteLine(Arg.Any()); } - public class GenericLoggerImpl : AutoSubstituteTest - { - private ITestOutputHelper _otherHelper; - - public GenericLoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) - { - _otherHelper = outputHelper; - } - - public void Write() - { - AutoSubstitute.Resolve>().LogError("abcd"); - AutoSubstitute.Resolve>().LogError("abcd {Something}", "somevalue"); - } - } - [Fact] public void Should_Inject_GenericLogger() { @@ -136,10 +65,11 @@ public void Should_Not_Fake_Optional_Parameters() public void Should_Populate_Optional_Parameters_When_Provided() { AutoSubstitute.Provide(new MyItem()); - AutoSubstitute.Resolve() - .Item - .Should() - .NotBeNull(); + AutoSubstitute + .Resolve() + .Item + .Should() + .NotBeNull(); } [Fact] @@ -150,21 +80,79 @@ public void Should_Fail_If_Container_Is_Touched_When_Building() a.Should().Throw(); } - private class MyItem : IItem + public AutoSubstituteTestTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + private class Impl : AutoSubstituteTest { + public Impl(ITestOutputHelper outputHelper) : base(outputHelper) + { + Logger.LogError("abcd"); + Logger.LogError("abcd {Something}", "somevalue"); + } } - public interface IItem + private class DoubleAccess : AutoSubstituteTest { + public DoubleAccess(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public IContainer Self => Container; + + protected override IContainer BuildContainer(IContainer container) + { + // invalid do not touch ServiceProvider or Container while constructing the container.... + return Container.GetRequiredService(); + } } - private class Optional + private class LoggerImpl : AutoSubstituteTest { - public IItem? Item { get; } + public LoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public void Write() + { + AutoSubstitute.Resolve().LogError("abcd"); + AutoSubstitute.Resolve().LogError("abcd {Something}", "somevalue"); + } + } + + private class LoggerFactoryImpl : AutoSubstituteTest + { + public LoggerFactoryImpl(ITestOutputHelper outputHelper) : base(outputHelper) { } + + public void Write() + { + AutoSubstitute.Resolve().CreateLogger("").LogError("abcd"); + AutoSubstitute.Resolve().CreateLogger("").LogError("abcd {Something}", "somevalue"); + } + } + + public class GenericLoggerImpl : AutoSubstituteTest + { + private ITestOutputHelper _otherHelper; + public GenericLoggerImpl(ITestOutputHelper outputHelper) : base(outputHelper) + { + _otherHelper = outputHelper; + } + + public void Write() + { + AutoSubstitute.Resolve>().LogError("abcd"); + AutoSubstitute.Resolve>().LogError("abcd {Something}", "somevalue"); + } + } + + private class MyItem : IItem { } + + public interface IItem { } + + private class Optional + { public Optional(IItem? item = null) { Item = item; } + + public IItem? Item { get; } } -} +} \ No newline at end of file diff --git a/test/Testing.Tests/XUnitExtensionsTests.cs b/test/Testing.Tests/XUnitExtensionsTests.cs index bbb7e5abd5..ecb5897e6d 100644 --- a/test/Testing.Tests/XUnitExtensionsTests.cs +++ b/test/Testing.Tests/XUnitExtensionsTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using Xunit; using Xunit.Abstractions; namespace Rocket.Surgery.Extensions.Testing.Tests;