Skip to content

Commit 824f9c4

Browse files
authored
Merge pull request #28
* Initial * Adding out parameters to mock factory * Pre merge cleanup * Namespace fix * Update to version 0.9.13 * Cleanup ConstructorBuilder * Cleanup warnings * Cleanup code and adding documentation * Adding documentation to files
1 parent f947925 commit 824f9c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1531
-1013
lines changed

.editorconfig

Lines changed: 56 additions & 56 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MiniMock
22

3-
Mini mock offers a _minimalistic_ approach to mocking in .Net. It is designed to be simple to use and easy to understand.
3+
Mini mock offers a _minimalistic_ approach to mocking in .Net. It is designed to be simple to use and easy to understand.
44
It is not as feature rich as other mocking frameworks, but aims to solve __95%__ of the use cases. For the remaining __5%__ you should consider creating a custom mock.
55

66
Mini mock is __extremely strict__ requiring you to specify all features you want to mock. This is by design to make sure you are aware of what you are mocking.
@@ -64,7 +64,7 @@ public void MyTest {
6464
}
6565
```
6666

67-
Create a mock by using the mock factory
67+
Create a mock by using the mock factory
6868

6969
```csharp
7070
var mockRepository = Mock.IMyRepository();
@@ -207,7 +207,7 @@ Mocking indexers is supported either by overloading the get and set methods or b
207207
}
208208
```
209209

210-
### Raising events
210+
### Raising events
211211

212212
Raise events using an event trigger.
213213

@@ -223,15 +223,15 @@ Raise events using an event trigger.
223223

224224
### Argument matching
225225

226-
MiniMock does not support argument matching using matchers like other mocking frameworks.
226+
MiniMock does not support argument matching using matchers like other mocking frameworks.
227227
Instead, you can use the call parameter to match arguments using predicates or internal functions.
228228

229229
```csharp
230230
var versionLibrary = Mock.IVersionLibrary(config => config
231231
.DownloadExists(call: version => version is { Major: 2, Minor: 0 }) // Returns true for version 2.0.x based on a version parameter
232232
);
233233
```
234-
234+
235235
__using internal functions__
236236

237237
```csharp

src/MiniMock/AnalyzerReleases.Shipped.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
### New Rules
44

5-
Rule ID | Category | Severity | Notes
6-
--------|----------|----------|--------------------
7-
MM0002 | Usage | Error | Ref properties not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0002.md)
8-
MM0003 | Usage | Error | Ref return type not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0003.md)
9-
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
10-
MM0005 | Usage | Error | Static abstract members not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0005.md)
11-
MM0006 | Usage | Error | Can not create mock for a sealed class, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0006.md)
5+
Rule ID | Category | Severity | Notes
6+
---------|----------|----------|------------------------------------------------------------------------------------------------------------------------------------------------
7+
MM0002 | Usage | Error | Ref properties not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0002.md)
8+
MM0003 | Usage | Error | Ref return type not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0003.md)
9+
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
10+
MM0005 | Usage | Error | Static abstract members not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0005.md)
11+
MM0006 | Usage | Error | Can not create mock for a sealed class, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0006.md)
1212

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
### Removed Rules
22

3-
Rule ID | Category | Severity | Notes
4-
--------|----------|----------|--------------------
5-
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)
3+
Rule ID | Category | Severity | Notes
4+
---------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------
5+
MM0004 | Usage | Error | Generic method not supported, [Documentation](https://github.com/oswaldsql/MiniMock/blob/main/Documentation/AnalyzerRules/MM0004.md)

src/MiniMock/Builders/ClassBuilder.cs

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
1-
namespace MiniMock.Builders;
2-
31
using System;
42
using System.Collections.Generic;
53
using System.Linq;
64
using Microsoft.CodeAnalysis;
7-
5+
using MiniMock;
6+
using MiniMock.Builders;
7+
using MiniMock.Util;
8+
9+
/// <summary>
10+
/// ClassBuilder is responsible for generating mock classes for a given target symbol.
11+
/// </summary>
12+
/// <param name="target">The target symbol to build the mock class for.</param>
813
internal class ClassBuilder(ISymbol target)
914
{
15+
/// <summary>
16+
/// Array of builders for different member types.
17+
/// </summary>
18+
private static readonly ISymbolBuilder[] Builders = [new ConstructorBuilder(), new EventBuilder(), new MethodBuilder(), new PropertyBuilder(), new IndexBuilder()];
19+
20+
/// <summary>
21+
/// Filter to determine if a member's accessibility is public or protected.
22+
/// </summary>
23+
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
24+
25+
/// <summary>
26+
/// Builds a mock class for the given symbol.
27+
/// </summary>
28+
/// <param name="symbol">The symbol to build the mock class for.</param>
29+
/// <returns>The generated mock class as a string.</returns>
1030
public static string Build(ISymbol symbol) =>
1131
new ClassBuilder(symbol).BuildClass();
1232

33+
/// <summary>
34+
/// Generates the mock class code.
35+
/// </summary>
36+
/// <returns>The generated mock class as a string.</returns>
1337
private string BuildClass()
1438
{
1539
if (target.IsSealed && target is INamedTypeSymbol symbol)
@@ -35,6 +59,8 @@ private string BuildClass()
3559

3660
var documentationName = fullName.Replace("<", "{").Replace(">", "}");
3761

62+
var hasConstructors = ((INamedTypeSymbol)target).Constructors.Any(c => this.accessibilityFilter(c.DeclaredAccessibility));
63+
3864
builder.Add($$"""
3965
// Generated by MiniMock on {{DateTime.Now}}
4066
#nullable enable
@@ -50,18 +76,28 @@ internal class {{name}} : {{fullName}} {{constraints}}
5076
private Config _config { get; }
5177
internal void GetConfig(out Config config) => config = _config;
5278
79+
/// <summary>
80+
/// Configuration class for the mock.
81+
/// </summary>
5382
internal partial class Config
5483
{
5584
private readonly {{name}} target;
5685
86+
/// <summary>
87+
/// Initializes a new instance of the <see cref="Config"/> class.
88+
/// </summary>
89+
/// <param name="target">The target mock class.</param>
5790
public Config({{name}} target)
5891
{
5992
this.target = target;
6093
}
6194
}
6295
""");
6396

64-
new ConstructorBuilder(target).Build(builder, fullName, name);
97+
if (!hasConstructors)
98+
{
99+
builder.Add(ConstructorBuilder.BuildEmptyConstructor(target));
100+
}
65101

66102
this.BuildMembers(builder);
67103

@@ -75,8 +111,10 @@ public Config({{name}} target)
75111
return builder.ToString();
76112
}
77113

78-
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
79-
114+
/// <summary>
115+
/// Builds the members of the mock class.
116+
/// </summary>
117+
/// <param name="builder">The code builder to add the members to.</param>
80118
private void BuildMembers(CodeBuilder builder)
81119
{
82120
var memberCandidates = new List<ISymbol>(((INamedTypeSymbol)target).GetMembers().Where(t => this.accessibilityFilter(t.DeclaredAccessibility)));
@@ -91,24 +129,19 @@ private void BuildMembers(CodeBuilder builder)
91129
foreach (var members in memberGroups)
92130
{
93131
var symbol = members.First();
94-
switch (symbol)
132+
var wasBuild = Builders.FirstOrDefault(b => b.TryBuild(builder, members));
133+
if (wasBuild == null)
95134
{
96-
case IEventSymbol:
97-
EventBuilder.BuildEvents(builder, members.OfType<IEventSymbol>());
98-
break;
99-
case IMethodSymbol { MethodKind: MethodKind.Ordinary }:
100-
MethodBuilder.BuildMethods(builder, members.OfType<IMethodSymbol>());
101-
break;
102-
case IPropertySymbol { IsIndexer: false }:
103-
PropertyBuilder.BuildProperties(builder, members.OfType<IPropertySymbol>());
104-
break;
105-
case IPropertySymbol { IsIndexer: true }:
106-
IndexBuilder.BuildIndexes(builder, members.OfType<IPropertySymbol>());
107-
break;
135+
builder.Add("// Ignored " + symbol.Kind + " " + symbol);
108136
}
109137
}
110138
}
111139

140+
/// <summary>
141+
/// Adds inherited interfaces to the member candidates list.
142+
/// </summary>
143+
/// <param name="memberCandidates">The list of member candidates.</param>
144+
/// <param name="namedTypeSymbol">The named type symbol to add inherited interfaces from.</param>
112145
private void AddInheritedInterfaces(List<ISymbol> memberCandidates, INamedTypeSymbol namedTypeSymbol)
113146
{
114147
var allInterfaces = namedTypeSymbol.AllInterfaces;
Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,111 @@
11
namespace MiniMock.Builders;
22

3-
using System;
3+
using System.Collections.Generic;
44
using System.Linq;
55
using Microsoft.CodeAnalysis;
6+
using Util;
67

7-
internal class ConstructorBuilder(ISymbol target)
8+
/// <summary>
9+
/// Represents a builder for constructing mock constructors.
10+
/// </summary>
11+
internal class ConstructorBuilder : ISymbolBuilder
812
{
9-
private readonly Func<Accessibility, bool> accessibilityFilter = accessibility => accessibility == Accessibility.Public || accessibility == Accessibility.Protected;
13+
/// <summary>
14+
/// Tries to build constructors for the given symbols.
15+
/// </summary>
16+
/// <param name="builder">The code builder to add the constructors to.</param>
17+
/// <param name="symbols">The symbols to build constructors for.</param>
18+
/// <returns>True if constructors were built; otherwise, false.</returns>
19+
public bool TryBuild(CodeBuilder builder, IGrouping<string, ISymbol> symbols)
20+
{
21+
var first = symbols.First();
22+
if (first is IMethodSymbol { MethodKind: MethodKind.Constructor })
23+
{
24+
return BuildConstructors(builder, first.ContainingSymbol, symbols.OfType<IMethodSymbol>());
25+
}
1026

11-
public void Build(CodeBuilder builder, string fullName, string name)
27+
return false;
28+
}
29+
30+
/// <summary>
31+
/// Builds constructors for the specified target and adds them to the code builder.
32+
/// </summary>
33+
/// <param name="builder">The code builder to add the constructors to.</param>
34+
/// <param name="target">The target symbol for which to build constructors.</param>
35+
/// <param name="constructors">The constructors to build.</param>
36+
/// <returns>True if constructors were built; otherwise, false.</returns>
37+
private static bool BuildConstructors(CodeBuilder builder, ISymbol target, IEnumerable<IMethodSymbol> constructors)
1238
{
13-
var symbol = (INamedTypeSymbol)target;
39+
var fullName = target.ToString();
40+
var name = "MockOf_" + target.Name;
1441

15-
var constructors = symbol.Constructors
16-
.Where(c => this.accessibilityFilter(c.DeclaredAccessibility))
17-
.ToArray();
42+
var typeArguments = ((INamedTypeSymbol)target).TypeArguments;
43+
if (typeArguments.Length > 0)
44+
{
45+
var types = string.Join(", ", typeArguments.Select(t => t.Name));
46+
name = $"MockOf_{target.Name}<{types}>";
47+
}
1848

1949
builder.Add("#region Constructors");
2050

21-
if (constructors.Length == 0 || constructors.Any(t => t.Parameters.Length == 0))
51+
foreach (var constructor in constructors)
2252
{
53+
var parameterList = constructor.Parameters.ToString(p => $"{p.Type} {p.Name}, ", "");
54+
var argumentList = constructor.Parameters.ToString(p => p.Name);
55+
56+
var parameterNames = constructor.Parameters.ToString(p => p.Name + ", ", "");
57+
2358
builder.Add($$"""
24-
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
59+
internal protected MockOf_{{target.Name}}({{parameterList}}System.Action<Config>? config = null) : base({{argumentList}}) {
2560
var result = new Config(this);
2661
config = config ?? new System.Action<Config>(t => { });
2762
config.Invoke(result);
2863
_config = result;
2964
}
3065
31-
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
66+
public static {{fullName}} Create({{parameterList}}System.Action<Config>? config = null) => new {{name}}({{parameterNames}}config);
3267
""");
3368
}
3469

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);
70+
builder.Add("#endregion");
4171

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-
}
72+
return true;
73+
}
4974

50-
public static {{fullName}} Create({{parameterList}}, System.Action<Config>? config = null) => new {{name}}({{parameterNamesList}}, config);
51-
""");
75+
/// <summary>
76+
/// Builds an empty constructor for the specified target.
77+
/// </summary>
78+
/// <param name="target">The target symbol for which to build an empty constructor.</param>
79+
/// <returns>A code builder containing the empty constructor.</returns>
80+
public static CodeBuilder BuildEmptyConstructor(ISymbol target)
81+
{
82+
var fullName = target.ToString();
83+
var name = "MockOf_" + target.Name;
84+
85+
var typeArguments = ((INamedTypeSymbol)target).TypeArguments;
86+
if (typeArguments.Length > 0)
87+
{
88+
var types = string.Join(", ", typeArguments.Select(t => t.Name));
89+
name = $"MockOf_{target.Name}<{types}>";
5290
}
5391

54-
builder.Add("#endregion");
92+
CodeBuilder builder = new();
93+
94+
builder.Add($$"""
95+
#region Constructor
96+
97+
internal protected MockOf_{{target.Name}}(System.Action<Config>? config = null) {
98+
var result = new Config(this);
99+
config = config ?? new System.Action<Config>(t => { });
100+
config.Invoke(result);
101+
_config = result;
102+
}
103+
104+
public static {{fullName}} Create(System.Action<Config>? config = null) => new {{name}}(config);
105+
106+
#endregion
107+
""");
108+
109+
return builder;
55110
}
56111
}

src/MiniMock/Builders/Documentation.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ namespace MiniMock.Builders;
33
internal static class Documentation
44
{
55
internal const string CallBack = "Configures the mock to execute the specified action when the method matching the signature is called.";
6+
67
internal const string AcceptAny = "Configures the mock to accept any call to the method.";
8+
79
internal const string ThrowsException = "Configures the mock to throw the specified exception when the method is called.";
10+
811
internal const string SpecificValue = "Configures the mock to return the specific value.";
12+
913
internal const string SpecificValueList = "Configures the mock to return the consecutive values from an enumeration of values.";
14+
1015
internal const string GenericTaskObject = "Configures the mock to return the specific value in a task object.";
16+
1117
internal const string GenericTaskFunction = "Configures the mock to call the specified function and return the value wrapped in a task object when the method matching the signature is called.";
1218
}

0 commit comments

Comments
 (0)