private static void WriteUsings(StringBuilder builder, TestGenerationContext context) { List <string> namespaces = new List <string>(); namespaces.AddRange(MockFrameworkAbstraction.GetUsings(context.MockFramework)); namespaces.Add(TestFrameworkAbstraction.GetUsing(context.TestFramework)); namespaces.Add(context.ClassNamespace); foreach (InjectableType injectedType in context.InjectedTypes) { namespaces.AddRange(injectedType.TypeNamespaces); } namespaces = namespaces.Distinct().ToList(); namespaces.Sort(StringComparer.Ordinal); for (int i = 0; i < namespaces.Count; i++) { builder.Append($"using {namespaces[i]};"); if (i < namespaces.Count - 1) { builder.AppendLine(); } } }
private static void WriteExplicitConstructor(StringBuilder builder, TestGenerationContext context, string currentIndent) { builder.Append($"new {context.ClassName}"); if (context.ConstructorTypes.Count > 0) { builder.AppendLine("("); for (int i = 0; i < context.ConstructorTypes.Count; i++) { string mockReferenceStatement; InjectableType constructorType = context.ConstructorTypes[i]; if (constructorType == null) { mockReferenceStatement = "TODO"; } else { string template = StaticBoilerplateSettings.GetTemplate(context.MockFramework, TemplateType.MockObjectReference); mockReferenceStatement = template .Replace("$InterfaceName$", constructorType.TypeName) .Replace("$InterfaceNameBase$", constructorType.TypeBaseName); } builder.Append($"{currentIndent} {mockReferenceStatement}"); if (i < context.ConstructorTypes.Count - 1) { builder.AppendLine(","); } } builder.Append(")"); } else if (context.Properties.Count == 0) { builder.Append("()"); } if (context.Properties.Count > 0) { builder.AppendLine(); builder.AppendLine("{"); foreach (InjectableProperty property in context.Properties) { string template = StaticBoilerplateSettings.GetTemplate(context.MockFramework, TemplateType.MockObjectReference); string mockReferenceStatement = template .Replace("$InterfaceName$", property.TypeName) .Replace("$InterfaceNameBase$", property.TypeBaseName); builder.AppendLine($"{property.Name} = {mockReferenceStatement},"); } builder.Append(@"}"); } }
// Works for both field declarations and initializations. private static void WriteFieldLines(StringBuilder builder, TestGenerationContext context, string template) { for (int i = 0; i < context.InjectedTypes.Count; i++) { InjectableType injectedType = context.InjectedTypes[i]; string line = ReplaceInterfaceTokens(template, injectedType); builder.Append(line); if (i < context.InjectedTypes.Count - 1) { builder.AppendLine(); } } }
// Works for both field declarations and initializations. private static void WriteFieldLines(StringBuilder builder, TestGenerationContext context, string template) { for (int i = 0; i < context.InjectedTypes.Count; i++) { InjectableType injectedType = context.InjectedTypes[i]; string line = template .Replace("$InterfaceName$", injectedType.TypeName) .Replace("$InterfaceNameBase$", injectedType.TypeBaseName); builder.Append(line); if (i < context.InjectedTypes.Count - 1) { builder.AppendLine(); } } }
private static void WriteMockFieldInitializations(StringBuilder builder, TestGenerationContext context) { string template = StaticBoilerplateSettings.GetTemplate(context.MockFramework, TemplateType.MockFieldInitialization); WriteFieldLines(builder, context, template); }
private string GenerateUnitTestContents(TestGenerationContext context) { TestFramework testFramework = context.TestFramework; MockFramework mockFramework = context.MockFramework; string pascalCaseShortClassName = null; foreach (string suffix in ClassSuffixes) { if (className.EndsWith(suffix)) { pascalCaseShortClassName = suffix; break; } } if (pascalCaseShortClassName == null) { pascalCaseShortClassName = className; } string classVariableName = pascalCaseShortClassName.Substring(0, 1).ToLowerInvariant() + pascalCaseShortClassName.Substring(1); string fileTemplate = StaticBoilerplateSettings.GetTemplate(mockFramework, TemplateType.File); var builder = new StringBuilder(); for (int i = 0; i < fileTemplate.Length; i++) { char c = fileTemplate[i]; if (c == '$') { int endIndex = -1; for (int j = i + 1; j < fileTemplate.Length; j++) { if (fileTemplate[j] == '$') { endIndex = j; break; } } if (endIndex < 0) { // We couldn't find the end index for the replacement property name. Continue. builder.Append(c); } else { // Calculate values on demand from switch statement. Some are preset values, some need a bit of calc like base name, // some are dependent on the test framework (attributes), some need to pull down other templates and loop through mock fields string propertyName = fileTemplate.Substring(i + 1, endIndex - i - 1); switch (propertyName) { case "UsingStatements": WriteUsings(builder, context); break; case "Namespace": builder.Append(context.UnitTestNamespace); break; case "MockFieldDeclarations": WriteMockFieldDeclarations(builder, context); break; case "MockFieldInitializations": WriteMockFieldInitializations(builder, context); break; case "ExplicitConstructor": WriteExplicitConstructor(builder, context, FindIndent(fileTemplate, i)); break; case "ClassName": builder.Append(context.ClassName); break; case "ClassNameShort": builder.Append(GetShortClassName(context.ClassName)); break; case "ClassNameShortLower": builder.Append(GetShortClassNameLower(context.ClassName)); break; case "TestClassAttribute": builder.Append(TestFrameworkAbstraction.GetTestClassAttribute(testFramework)); break; case "TestInitializeAttribute": builder.Append(TestFrameworkAbstraction.GetTestInitializeAttribute(testFramework)); break; case "TestCleanupAttribute": builder.Append(TestFrameworkAbstraction.GetTestCleanupAttribute(testFramework)); break; case "TestMethodAttribute": builder.Append(TestFrameworkAbstraction.GetTestMethodAttribute(testFramework)); break; default: // We didn't recognize it, just pass through. builder.Append($"${propertyName}$"); break; } i = endIndex; } } else { builder.Append(c); } } SyntaxTree tree = CSharpSyntaxTree.ParseText(builder.ToString()); SyntaxNode formattedNode = Formatter.Format(tree.GetRoot(), CreateUnitTestBoilerplateCommandPackage.VisualStudioWorkspace); return(formattedNode.ToString()); }
private async Task <string> GenerateUnitTestContentsFromFileAsync(string inputFilePath) { Microsoft.CodeAnalysis.Solution solution = CreateUnitTestBoilerplateCommandPackage.VisualStudioWorkspace.CurrentSolution; DocumentId documentId = solution.GetDocumentIdsWithFilePath(inputFilePath).FirstOrDefault(); if (documentId == null) { this.HandleError("Could not find document in solution with file path " + inputFilePath); } var document = solution.GetDocument(documentId); SyntaxNode root = await document.GetSyntaxRootAsync(); SemanticModel semanticModel = await document.GetSemanticModelAsync(); SyntaxNode firstClassDeclaration = root.DescendantNodes().FirstOrDefault(node => node.Kind() == SyntaxKind.ClassDeclaration); if (firstClassDeclaration == null) { this.HandleError("Could not find class declaration."); } if (firstClassDeclaration.ChildTokens().Any(node => node.Kind() == SyntaxKind.AbstractKeyword)) { this.HandleError("Cannot unit test an abstract class."); } SyntaxToken classIdentifierToken = firstClassDeclaration.ChildTokens().FirstOrDefault(n => n.Kind() == SyntaxKind.IdentifierToken); if (classIdentifierToken == null) { this.HandleError("Could not find class identifier."); } NamespaceDeclarationSyntax namespaceDeclarationSyntax = null; if (!Utilities.TryGetParentSyntax(firstClassDeclaration, out namespaceDeclarationSyntax)) { this.HandleError("Could not find class namespace."); } // Find property injection types var injectableProperties = new List <InjectableProperty>(); string classFullName = namespaceDeclarationSyntax.Name + "." + classIdentifierToken; INamedTypeSymbol classType = semanticModel.Compilation.GetTypeByMetadataName(classFullName); foreach (ISymbol member in classType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers())) { if (member.Kind == SymbolKind.Property) { IPropertySymbol property = (IPropertySymbol)member; foreach (AttributeData attribute in property.GetAttributes()) { if (attribute.AttributeClass.ToString() == "Microsoft.Practices.Unity.DependencyAttribute") { injectableProperties.Add(new InjectableProperty(property.Name, property.Type.Name, property.Type.ContainingNamespace.ToString())); } } } } this.className = classIdentifierToken.ToString(); // Find constructor injection types var constructorInjectionTypes = new List <InjectableType>(); SyntaxNode constructorDeclaration = firstClassDeclaration.ChildNodes().FirstOrDefault(n => n.Kind() == SyntaxKind.ConstructorDeclaration); if (constructorDeclaration != null) { SyntaxNode parameterListNode = constructorDeclaration.ChildNodes().First(n => n.Kind() == SyntaxKind.ParameterList); var parameterNodes = parameterListNode.ChildNodes().Where(n => n.Kind() == SyntaxKind.Parameter); foreach (SyntaxNode node in parameterNodes) { SyntaxNode identifierNode = node.ChildNodes().FirstOrDefault(n => n.Kind() == SyntaxKind.IdentifierName); if (identifierNode != null) { SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(identifierNode); constructorInjectionTypes.Add( new InjectableType( symbolInfo.Symbol.Name, symbolInfo.Symbol.ContainingNamespace.ToString())); } else { constructorInjectionTypes.Add(null); } } } string unitTestNamespace; string defaultNamespace = this.SelectedProject.Project.Properties.Item("DefaultNamespace").Value as string; string projectName = this.SelectedProject.Project.Name; if (string.IsNullOrEmpty(defaultNamespace) || defaultNamespace[0] == '.') { defaultNamespace = projectName + defaultNamespace; } if (string.IsNullOrEmpty(this.relativePath)) { unitTestNamespace = defaultNamespace; } else { List <string> defaultNamespaceParts = defaultNamespace.Split('.').ToList(); List <string> unitTestNamespaceParts = new List <string>(defaultNamespaceParts); unitTestNamespaceParts.AddRange(this.relativePath.Split('\\')); unitTestNamespace = string.Join(".", unitTestNamespaceParts); } List <InjectableType> injectedTypes = new List <InjectableType>(injectableProperties); injectedTypes.AddRange(constructorInjectionTypes.Where(t => t != null)); // Compile information needed to generate the test var context = new TestGenerationContext( Utilities.FindMockFramework(this.SelectedProject.Project), Utilities.FindTestFramework(this.SelectedProject.Project), unitTestNamespace, this.className, namespaceDeclarationSyntax.Name.ToString(), injectableProperties, constructorInjectionTypes, injectedTypes); return(this.GenerateUnitTestContents(context)); }