Skip to content

Commit 1cc0824

Browse files
committed
Add MetaTest to check that all LibGit2Sharp public types are mockable
Partially fixes #186
1 parent 34a2229 commit 1cc0824

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
</Reference>
5858
</ItemGroup>
5959
<ItemGroup>
60+
<Compile Include="MetaFixture.cs" />
6061
<Compile Include="MockedRepositoryFixture.cs" />
6162
<Compile Include="ConfigurationFixture.cs" />
6263
<Compile Include="AttributesFixture.cs" />

LibGit2Sharp.Tests/MetaFixture.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Text;
2+
using Xunit;
3+
using System.Reflection;
4+
using System;
5+
using System.Linq;
6+
using System.Collections.Generic;
7+
8+
namespace LibGit2Sharp.Tests
9+
{
10+
public class MetaFixture
11+
{
12+
private static readonly Type[] excludedTypes = new[] { typeof(Repository) };
13+
14+
// Related to https://github.com/libgit2/libgit2sharp/pull/185
15+
[Fact]
16+
public void TypesInLibGit2SharpMustBeExtensibleInATestingContext()
17+
{
18+
var nonTestableTypes = new Dictionary<Type, IEnumerable<string>>();
19+
20+
IEnumerable<Type> libGit2SharpTypes = Assembly.GetAssembly(typeof(Repository)).GetExportedTypes().Where(t => !excludedTypes.Contains(t) && t.Namespace == typeof(Repository).Namespace);
21+
22+
foreach (Type type in libGit2SharpTypes)
23+
{
24+
if (type.IsInterface || type.IsEnum || IsStatic(type))
25+
continue;
26+
27+
ConstructorInfo[] publicConstructor = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
28+
if (publicConstructor.Any())
29+
{
30+
continue;
31+
}
32+
33+
var nonVirtualMethodNamesForType = GetNonVirtualPublicMethodsNames(type).ToList();
34+
if (nonVirtualMethodNamesForType.Any())
35+
{
36+
nonTestableTypes.Add(type, nonVirtualMethodNamesForType);
37+
continue;
38+
}
39+
40+
if (!HasEmptyProtectedConstructor(type))
41+
{
42+
nonTestableTypes.Add(type, new List<string>());
43+
}
44+
}
45+
46+
if (nonTestableTypes.Any())
47+
{
48+
Assert.True(false, Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes));
49+
}
50+
}
51+
52+
private static string BuildNonTestableTypesMessage(Dictionary<Type, IEnumerable<string>> nonTestableTypes)
53+
{
54+
var sb = new StringBuilder();
55+
56+
foreach (var kvp in nonTestableTypes)
57+
{
58+
sb.AppendFormat("'{0}' cannot be easily abstracted in a testing context. Please make sure it either has a public constructor, or an empty protected constructor.{1}",
59+
kvp.Key, Environment.NewLine);
60+
61+
foreach (string methodName in kvp.Value)
62+
{
63+
sb.AppendFormat(" - Method '{0}' must be virtual{1}", methodName, Environment.NewLine);
64+
}
65+
}
66+
67+
return sb.ToString();
68+
}
69+
70+
private static IEnumerable<string> GetNonVirtualPublicMethodsNames(Type type)
71+
{
72+
var publicMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
73+
74+
return from mi in publicMethods where !mi.IsVirtual && !mi.IsStatic select mi.ToString();
75+
}
76+
77+
private static bool HasEmptyProtectedConstructor(Type type)
78+
{
79+
ConstructorInfo[] nonPublicConstructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
80+
81+
return nonPublicConstructors.Any(ci => !ci.IsPrivate && !ci.IsAssembly && !ci.IsFinal && !ci.GetParameters().Any());
82+
}
83+
84+
private static bool IsStatic(Type type)
85+
{
86+
return type.IsAbstract && type.IsSealed;
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)