Skip to content

Commit f947925

Browse files
oswaldsqlLasse Sjørup
andauthored
Version 9.12 (#27)
* WIP * Visibility of ctors * Cleaning up solution before release * Fixing resharper warnings --------- Co-authored-by: Lasse Sjørup <lasse.sjoerup.ext@siemensgamesa.com>
1 parent 3fddd9c commit f947925

34 files changed

+503
-82
lines changed

src/MiniMock/Builders/ClassBuilder.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,6 @@ namespace {{interfaceNamespace}}
4747
internal class {{name}} : {{fullName}} {{constraints}}
4848
{
4949
->
50-
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
51-
var result = new Config(this);
52-
config = config ?? new System.Action<Config>(t => { });
53-
config.Invoke(result);
54-
_config = result;
55-
}
56-
57-
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
58-
5950
private Config _config { get; }
6051
internal void GetConfig(out Config config) => config = _config;
6152
@@ -70,6 +61,8 @@ public Config({{name}} target)
7061
}
7162
""");
7263

64+
new ConstructorBuilder(target).Build(builder, fullName, name);
65+
7366
this.BuildMembers(builder);
7467

7568
builder.Add("""
@@ -82,9 +75,11 @@ public Config({{name}} target)
8275
return builder.ToString();
8376
}
8477

78+
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
79+
8580
private void BuildMembers(CodeBuilder builder)
8681
{
87-
var memberCandidates = new List<ISymbol>(((INamedTypeSymbol)target).GetMembers());
82+
var memberCandidates = new List<ISymbol>(((INamedTypeSymbol)target).GetMembers().Where(t => this.accessibilityFilter(t.DeclaredAccessibility)));
8883

8984
if (((INamedTypeSymbol)target).TypeKind == TypeKind.Interface)
9085
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
namespace MiniMock.Builders;
2+
3+
using System;
4+
using System.Linq;
5+
using Microsoft.CodeAnalysis;
6+
7+
internal class ConstructorBuilder(ISymbol target)
8+
{
9+
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
10+
11+
public void Build(CodeBuilder builder, string fullName, string name)
12+
{
13+
var symbol = (INamedTypeSymbol)target;
14+
15+
var constructors = symbol.Constructors
16+
.Where(c => this.accessibilityFilter(c.DeclaredAccessibility))
17+
.ToArray();
18+
19+
builder.Add("#region Constructors");
20+
21+
if (constructors.Length == 0 || constructors.Any(t => t.Parameters.Length == 0))
22+
{
23+
builder.Add($$"""
24+
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
25+
var result = new Config(this);
26+
config = config ?? new System.Action<Config>(t => { });
27+
config.Invoke(result);
28+
_config = result;
29+
}
30+
31+
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
32+
""");
33+
}
34+
35+
foreach (var constructor in constructors.Where(t => t.Parameters.Length > 0))
36+
{
37+
var parameters = constructor.Parameters.Select(p => $"{p.Type} {p.Name}").ToArray();
38+
var parameterList = string.Join(", ", parameters);
39+
var parameterNames = constructor.Parameters.Select(p => p.Name).ToArray();
40+
var parameterNamesList = string.Join(", ", parameterNames);
41+
42+
builder.Add($$"""
43+
internal protected MockOf_{{target.Name}}({{parameterList}}, System.Action<Config>? config = null) : base({{parameterNamesList}}) {
44+
var result = new Config(this);
45+
config = config ?? new System.Action<Config>(t => { });
46+
config.Invoke(result);
47+
_config = result;
48+
}
49+
50+
public static {{fullName}} Create({{parameterList}}, System.Action<Config>? config = null) => new {{name}}({{parameterNamesList}}, config);
51+
""");
52+
}
53+
54+
builder.Add("#endregion");
55+
}
56+
}

src/MiniMock/Builders/MockClassBuilder.cs

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace MiniMock.Builders;
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Collections.Immutable;
56
using System.Linq;
67
using Microsoft.CodeAnalysis;
78

@@ -11,75 +12,116 @@ public static string Build(IEnumerable<ISymbol> typeSymbols, SourceProductionCon
1112
{
1213
var mocks = typeSymbols.OfType<INamedTypeSymbol>().OrderBy(t => t.Name).ToArray();
1314

14-
if(!mocks.Any())
15+
if (!mocks.Any())
1516
{
1617
return "//No mocks found.";
1718
}
1819

1920
var builder = new CodeBuilder();
2021

2122
builder.Add($$"""
22-
// Generated by MiniMock on {{DateTime.Now}}
23-
#nullable enable
24-
namespace MiniMock {
25-
->
26-
27-
/// <summary>
28-
/// Factory for creating mock objects.
29-
/// </summary>
30-
internal static class Mock {
31-
->
32-
""");
23+
// Generated by MiniMock on {{DateTime.Now}}
24+
#nullable enable
25+
namespace MiniMock {
26+
->
27+
28+
/// <summary>
29+
/// Factory for creating mock objects.
30+
/// </summary>
31+
internal static class Mock {
32+
->
33+
""");
3334

3435
foreach (var symbol in mocks)
3536
{
36-
var typeArguments = symbol.TypeArguments;
37-
var containingNamespace = symbol.ContainingNamespace;
38-
var symbolName = symbol.Name;
39-
40-
if (typeArguments.Length > 0)
37+
bool AccessibilityFilter(Accessibility accessibility)
4138
{
42-
var types = string.Join(", ", typeArguments.Select(t => t.Name));
43-
var name = $"MockOf_{symbolName}<{types}>";
44-
var methodName = symbolName;
45-
var constraints = typeArguments.ToConstraints();
46-
47-
var cref = symbol.ToString().Replace('<','{').Replace('>','}');
48-
builder.Add(
49-
$"""
39+
return accessibility is Accessibility.Public or Accessibility.Protected;
40+
}
5041

51-
/// <summary>
52-
/// Creates a mock object for <see cref="{cref}"/>.
53-
/// </summary>
54-
/// <param name="config">Optional configuration for the mock object.</param>
55-
/// <returns>The mock object for <see cref="{cref}"/>.</returns>
56-
""");
57-
builder.Add($"internal static {symbol} {methodName}<{types}>(System.Action<{containingNamespace}.{name}.Config>? config = null) {constraints} => {containingNamespace}.{name}.Create(config);");
42+
if (!symbol.Constructors.Any(t => !t.IsStatic))
43+
{
44+
BuildFactoryMethod(symbol, builder);
5845
}
5946
else
6047
{
61-
builder.Add(
62-
$"""
63-
64-
/// <summary>
65-
/// Creates a mock object for <see cref="{symbol}"/>.
66-
/// </summary>
67-
/// <param name="config">Optional configuration for the mock object.</param>
68-
/// <returns>The mock object for <see cref="{symbol}"/>.</returns>
69-
""");
70-
var name = "MockOf_" + symbolName;
71-
var methodName = symbolName;
72-
builder.Add($"internal static {symbol} {methodName}(System.Action<{containingNamespace}.{name}.Config>? config = null) => {containingNamespace}.{name}.Create(config);");
48+
foreach (var constructor in symbol.Constructors.Where(t => AccessibilityFilter(t.DeclaredAccessibility) && !t.IsStatic))
49+
{
50+
BuildFactoryMethod(symbol, builder, constructor);
51+
}
7352
}
7453
}
7554

7655
builder.Add("""
77-
<-
78-
}
79-
<-
80-
}
81-
""");
56+
<-
57+
}
58+
<-
59+
}
60+
""");
8261

8362
return builder.ToString();
8463
}
64+
65+
private static void BuildFactoryMethod(INamedTypeSymbol symbol, CodeBuilder builder, IMethodSymbol? constructor = null)
66+
{
67+
var p = constructor?.Parameters.Select(t => $"{t.Type} {t.Name}, ") ?? ImmutableArray<string>.Empty;
68+
var parameters = string.Join("", p);
69+
70+
var n = constructor?.Parameters.Select(t => $"{t.Name}, ") ?? ImmutableArray<string>.Empty;
71+
var names = string.Join("", n);
72+
73+
//var doc = constructor?.GetDocumentationCommentXml() ?? "/// DOC";
74+
//builder.Add(doc);
75+
76+
var typeArguments = symbol.TypeArguments;
77+
var containingNamespace = symbol.ContainingNamespace;
78+
var symbolName = symbol.Name;
79+
80+
var cref = symbol.ToString().Replace('<', '{').Replace('>', '}');
81+
82+
builder.Add(
83+
$"""
84+
85+
/// <summary>
86+
/// Creates a mock object for <see cref="{cref}"/>.
87+
/// </summary>
88+
""");
89+
90+
if (constructor != null)
91+
{
92+
foreach (var o in constructor.Parameters)
93+
{
94+
builder.Add($"/// <param name=\"{o.Name}\">Base constructor parameter {o.Name}.</param>");
95+
}
96+
}
97+
98+
builder.Add(
99+
$"""
100+
/// <param name="config">Optional configuration for the mock object.</param>
101+
/// <returns>The mock object for <see cref="{cref}"/>.</returns>
102+
""");
103+
104+
if (typeArguments.Length > 0)
105+
{
106+
var types = string.Join(", ", typeArguments.Select(t => t.Name));
107+
var name = $"MockOf_{symbolName}<{types}>";
108+
var constraints = typeArguments.ToConstraints();
109+
110+
builder.Add($"""
111+
internal static {symbol} {symbolName}<{types}>
112+
({parameters}System.Action<{containingNamespace}.{name}.Config>? config = null)
113+
{constraints}
114+
=> {containingNamespace}.{name}.Create({names}config);
115+
""");
116+
}
117+
else
118+
{
119+
var name = "MockOf_" + symbolName;
120+
builder.Add($"""
121+
internal static {symbol} {symbolName}
122+
({parameters}System.Action<{containingNamespace}.{name}.Config>? config = null)
123+
=> {containingNamespace}.{name}.Create({names}config);
124+
""");
125+
}
126+
}
85127
}

src/MiniMock/MiniMock.csproj

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Nullable>enable</Nullable>
77
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
88
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
9-
<Version>0.9.10</Version>
9+
<Version>0.9.12</Version>
1010
<Title>Mini mock</Title>
1111
<Authors>Lasse Sjørup</Authors>
1212
<Description>A minimalistic source generator for creating mocks for testing is a tool that automatically generates mock implementations of interfaces or classes. This helps in unit testing by providing a way to simulate the behavior of complex dependencies.</Description>
@@ -23,6 +23,16 @@
2323
<IncludeBuiltOutput>false</IncludeBuiltOutput>
2424
</PropertyGroup>
2525

26+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
27+
<DocumentationFile></DocumentationFile>
28+
<DebugSymbols>false</DebugSymbols>
29+
</PropertyGroup>
30+
31+
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
32+
<DocumentationFile></DocumentationFile>
33+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
34+
</PropertyGroup>
35+
2636
<ItemGroup>
2737
<Compile Remove="bin\**" />
2838
<EmbeddedResource Remove="bin\**" />
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// ReSharper disable EmptyConstructor
2+
// ReSharper disable MemberCanBeProtected.Global
3+
// ReSharper disable UnusedParameter.Local
4+
// ReSharper disable UnusedMember.Global
5+
// ReSharper disable UnusedMember.Local
6+
namespace MiniMock.Tests.ConstructorTests;
7+
8+
using System.Reflection;
9+
10+
public class AccessLevelTests
11+
{
12+
internal class AccessLevelTestClass
13+
{
14+
static AccessLevelTestClass() {}
15+
16+
public AccessLevelTestClass(bool publicCtor) { }
17+
18+
protected AccessLevelTestClass(string protectedCtor) { }
19+
20+
internal AccessLevelTestClass(int internalCtor) { }
21+
22+
private AccessLevelTestClass(double privateCtor) { }
23+
}
24+
25+
[Fact]
26+
[Mock<AccessLevelTestClass>]
27+
public void OnlyPublicAndProtectedCtorAreExposed()
28+
{
29+
// Arrange
30+
31+
// ACT
32+
var actual = typeof(MockOf_AccessLevelTestClass).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
33+
34+
// Assert
35+
Assert.Equal(2, actual.Length);
36+
}
37+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace MiniMock.Tests.ConstructorTests;
2+
3+
public class ParameterLessTests
4+
{
5+
public class ParameterLessClass
6+
{
7+
public ParameterLessClass() => this.CtorIsCalled = true;
8+
9+
public bool CtorIsCalled { get; set; }
10+
11+
public bool ImplicitCtorIsCalled { get; set; } = true;
12+
}
13+
14+
public interface IInterfaceWithoutCtor
15+
{
16+
}
17+
18+
[Fact]
19+
[Mock<ParameterLessClass>]
20+
public void ParameterlessConstructorCanBeUsed()
21+
{
22+
// Arrange
23+
24+
// ACT
25+
var sut = Mock.ParameterLessClass();
26+
27+
// Assert
28+
Assert.IsAssignableFrom<ParameterLessClass>(sut);
29+
Assert.True(sut.CtorIsCalled);
30+
Assert.True(sut.ImplicitCtorIsCalled);
31+
}
32+
33+
[Fact]
34+
[Mock<IInterfaceWithoutCtor>]
35+
public void InterfaceWithoutCtorCanBeUsed()
36+
{
37+
// Arrange
38+
39+
// ACT
40+
var sut = Mock.IInterfaceWithoutCtor();
41+
42+
// Assert
43+
Assert.IsAssignableFrom<IInterfaceWithoutCtor>(sut);
44+
}
45+
}

0 commit comments

Comments
 (0)