Skip to content

Commit

Permalink
Refactor test cases to matrix on Moq version (#82)
Browse files Browse the repository at this point in the history
Refactor test cases so that they test with an "old" version of Moq
(4.8.2) and now also a "new" version of Moq (4.18.4).

Fixes #58 

4.8.2 was the pre-existing test version. Any version prior to 4.13.1
works. (4.13.1 changed the internal implementation of `.As<T>()` [see
devlooped/moq@b552aed]).

4.18.4 is the most downloaded version of Moq.
  • Loading branch information
MattKotsenas authored Jun 12, 2024
1 parent dad8699 commit 5057438
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 180 deletions.
18 changes: 10 additions & 8 deletions Source/Moq.Analyzers.Test/AsAcceptOnlyInterfaceAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.CodeAnalysis.Testing;
using Verifier = Moq.Analyzers.Test.Helpers.AnalyzerVerifier<Moq.Analyzers.AsShouldBeUsedOnlyForInterfaceAnalyzer>;

namespace Moq.Analyzers.Test;
Expand All @@ -6,20 +7,20 @@ public class AsAcceptOnlyInterfaceAnalyzerTests
{
public static IEnumerable<object[]> TestData()
{
foreach (var @namespace in new[] { string.Empty, "namespace MyNamespace;" })
return new object[][]
{
// TODO: .As<BaseSampleClass> and .As<SampleClass> feels redundant
yield return [@namespace, """new Mock<BaseSampleClass>().As<{|Moq1300:BaseSampleClass|}>();"""];
yield return [@namespace, """new Mock<BaseSampleClass>().As<{|Moq1300:SampleClass|}>();"""];
yield return [@namespace, """new Mock<SampleClass>().As<ISampleInterface>();"""];
["""new Mock<BaseSampleClass>().As<{|Moq1300:BaseSampleClass|}>();"""],
["""new Mock<BaseSampleClass>().As<{|Moq1300:SampleClass|}>();"""],
["""new Mock<SampleClass>().As<ISampleInterface>();"""],
// TODO: Testing with .Setup() and .Returns() seems unnecessary.
yield return [@namespace, """new Mock<SampleClass>().As<ISampleInterface>().Setup(x => x.Calculate(It.IsAny<int>(), It.IsAny<int>())).Returns(10);"""];
}
["""new Mock<SampleClass>().As<ISampleInterface>().Setup(x => x.Calculate(It.IsAny<int>(), It.IsAny<int>())).Returns(10);"""],
}.WithNamespaces().WithReferenceAssemblyGroups();
}

[Theory]
[MemberData(nameof(TestData))]
public async Task ShouldAnalyzeAs(string @namespace, string mock)
public async Task ShouldAnalyzeAs(string referenceAssemblyGroup, string @namespace, string mock)
{
await Verifier.VerifyAnalyzerAsync(
$$"""
Expand Down Expand Up @@ -48,6 +49,7 @@ private void Test()
{{mock}}
}
}
""");
""",
referenceAssemblyGroup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,125 +6,78 @@ public class CallbackSignatureShouldMatchMockedMethodCodeFixTests
{
public static IEnumerable<object[]> TestData()
{
foreach (string @namespace in new[] { string.Empty, "namespace MyNamespace;" })
return new object[][]
{
yield return
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Returns((string s) => { return 0; });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Returns((string s) => { return 0; });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Returns((int i, string s, DateTime dt) => { return 0; });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Returns((int i, string s, DateTime dt) => { return 0; });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Returns((List<string> l) => { return 0; });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Returns((List<string> l) => { return 0; });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback({|Moq1100:(int i)|} => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback((string s) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback({|Moq1100:(string s1, string s2)|} => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback((string s) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback({|Moq1100:(string s1, int i1)|} => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback((int i, string s, DateTime dt) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback({|Moq1100:(int i)|} => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback((List<string> l) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback((string s) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback((string s) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback((int i, string s, DateTime dt) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback((int i, string s, DateTime dt) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback((List<string> l) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback((List<string> l) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback(() => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Callback(() => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback(() => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Callback(() => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback(() => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Callback(() => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Returns(0).Callback((string s) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<string>())).Returns(0).Callback((string s) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Returns(0).Callback((int i, string s, DateTime dt) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<DateTime>())).Returns(0).Callback((int i, string s, DateTime dt) => { });""",
];

yield return
],
[
@namespace,
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Returns(0).Callback((List<string> l) => { });""",
"""new Mock<IFoo>().Setup(x => x.Do(It.IsAny<List<string>>())).Returns(0).Callback((List<string> l) => { });""",
];
}
],
}.WithNamespaces().WithReferenceAssemblyGroups();
}

[Theory]
[MemberData(nameof(TestData))]
public async Task ShouldSuggestQuickFixWhenIncorrectCallbacks(string @namespace, string original, string quickFix)
public async Task ShouldSuggestQuickFixWhenIncorrectCallbacks(string referenceAssemblyGroup, string @namespace, string original, string quickFix)
{
static string Template(string ns, string mock) =>
$$"""
Expand All @@ -148,6 +101,6 @@ private void Test()
}
""";

await Verifier.VerifyCodeFixAsync(Template(@namespace, original), Template(@namespace, quickFix));
await Verifier.VerifyCodeFixAsync(Template(@namespace, original), Template(@namespace, quickFix), referenceAssemblyGroup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,53 @@ public class ConstructorArgumentsShouldMatchAnalyzerTests
{
public static IEnumerable<object[]> TestData()
{
foreach (var @namespace in new[] { string.Empty, "namespace MyNamespace;" })
return new object[][]
{
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default);"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Strict);"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Loose);"""];
yield return [@namespace, """new Mock<Foo>("3");"""];
yield return [@namespace, """new Mock<Foo>("4");"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, "5");"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, "6");"""];
yield return [@namespace, """new Mock<Foo>(false, 0);"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, true, 1);"""];
yield return [@namespace, """new Mock<Foo>(DateTime.Now, DateTime.Now);"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, DateTime.Now, DateTime.Now);"""];
yield return [@namespace, """new Mock<Foo>(new List<string>(), "7");"""];
yield return [@namespace, """new Mock<Foo>(new List<string>());"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, new List<string>(), "8");"""];
yield return [@namespace, """new Mock<Foo>(MockBehavior.Default, new List<string>());"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(1, true)|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(2, true)|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:("1", 3)|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(new int[] { 1, 2, 3 })|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(MockBehavior.Strict, 4, true)|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(MockBehavior.Loose, 5, true)|};"""];
yield return [@namespace, """new Mock<Foo>{|Moq1002:(MockBehavior.Loose, "2", 6)|};"""];
yield return [@namespace, """new Mock<AbstractGenericClassWithCtor<object>>{|Moq1002:("42")|};"""];
yield return [@namespace, """new Mock<AbstractGenericClassWithCtor<object>>{|Moq1002:("42", 42)|};"""];
yield return [@namespace, """new Mock<AbstractGenericClassDefaultCtor<object>>{|Moq1002:(42)|};"""];
yield return [@namespace, """new Mock<AbstractGenericClassDefaultCtor<object>>();"""];
yield return [@namespace, """new Mock<AbstractGenericClassDefaultCtor<object>>(MockBehavior.Default);"""];
["""new Mock<Foo>(MockBehavior.Default);"""],
["""new Mock<Foo>(MockBehavior.Strict);"""],
["""new Mock<Foo>(MockBehavior.Loose);"""],
["""new Mock<Foo>("3");"""],
["""new Mock<Foo>("4");"""],
["""new Mock<Foo>(MockBehavior.Default, "5");"""],
["""new Mock<Foo>(MockBehavior.Default, "6");"""],
["""new Mock<Foo>(false, 0);"""],
["""new Mock<Foo>(MockBehavior.Default, true, 1);"""],
["""new Mock<Foo>(DateTime.Now, DateTime.Now);"""],
["""new Mock<Foo>(MockBehavior.Default, DateTime.Now, DateTime.Now);"""],
["""new Mock<Foo>(new List<string>(), "7");"""],
["""new Mock<Foo>(new List<string>());"""],
["""new Mock<Foo>(MockBehavior.Default, new List<string>(), "8");"""],
["""new Mock<Foo>(MockBehavior.Default, new List<string>());"""],
["""new Mock<Foo>{|Moq1002:(1, true)|};"""],
["""new Mock<Foo>{|Moq1002:(2, true)|};"""],
["""new Mock<Foo>{|Moq1002:("1", 3)|};"""],
["""new Mock<Foo>{|Moq1002:(new int[] { 1, 2, 3 })|};"""],
["""new Mock<Foo>{|Moq1002:(MockBehavior.Strict, 4, true)|};"""],
["""new Mock<Foo>{|Moq1002:(MockBehavior.Loose, 5, true)|};"""],
["""new Mock<Foo>{|Moq1002:(MockBehavior.Loose, "2", 6)|};"""],
["""new Mock<AbstractGenericClassWithCtor<object>>{|Moq1002:("42")|};"""],
["""new Mock<AbstractGenericClassWithCtor<object>>{|Moq1002:("42", 42)|};"""],
["""new Mock<AbstractGenericClassDefaultCtor<object>>{|Moq1002:(42)|};"""],
["""new Mock<AbstractGenericClassDefaultCtor<object>>();"""],
["""new Mock<AbstractGenericClassDefaultCtor<object>>(MockBehavior.Default);"""],
// TODO: "I think this _should_ fail, but currently passes. Tracked by #55."
// yield return [@namespace, """new Mock<AbstractClassWithCtor>();"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>{|Moq1002:("42")|};"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>{|Moq1002:("42", 42)|};"""];
yield return [@namespace, """new Mock<AbstractClassDefaultCtor>{|Moq1002:(42)|};"""];
yield return [@namespace, """new Mock<AbstractClassDefaultCtor>();"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>(42);"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42);"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>(42, "42");"""];
yield return [@namespace, """new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42, "42");"""];
yield return [@namespace, """new Mock<AbstractGenericClassWithCtor<object>>(42);"""];
yield return [@namespace, """new Mock<AbstractGenericClassWithCtor<object>>(MockBehavior.Default, 42);"""];
}
// ["""new Mock<AbstractClassWithCtor>();"""],
["""new Mock<AbstractClassWithCtor>{|Moq1002:("42")|};"""],
["""new Mock<AbstractClassWithCtor>{|Moq1002:("42", 42)|};"""],
["""new Mock<AbstractClassDefaultCtor>{|Moq1002:(42)|};"""],
["""new Mock<AbstractClassDefaultCtor>();"""],
["""new Mock<AbstractClassWithCtor>(42);"""],
["""new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42);"""],
["""new Mock<AbstractClassWithCtor>(42, "42");"""],
["""new Mock<AbstractClassWithCtor>(MockBehavior.Default, 42, "42");"""],
["""new Mock<AbstractGenericClassWithCtor<object>>(42);"""],
["""new Mock<AbstractGenericClassWithCtor<object>>(MockBehavior.Default, 42);"""],
}.WithNamespaces().WithReferenceAssemblyGroups();
}

[Theory]
[MemberData(nameof(TestData))]
public async Task ShouldAnalyzeConstructorArguments(string @namespace, string mock)
public async Task ShouldAnalyzeConstructorArguments(string referenceAssemblyGroup, string @namespace, string mock)
{
await Verifier.VerifyAnalyzerAsync(
$$"""
Expand Down Expand Up @@ -95,6 +95,7 @@ private void Test()
{{mock}}
}
}
""");
""",
referenceAssemblyGroup);
}
}
8 changes: 5 additions & 3 deletions Source/Moq.Analyzers.Test/Helpers/AnalyzerVerifier.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;

namespace Moq.Analyzers.Test.Helpers;

internal static class AnalyzerVerifier<TAnalyzer>
where TAnalyzer : DiagnosticAnalyzer, new()
{
public static async Task VerifyAnalyzerAsync(string source)
public static async Task VerifyAnalyzerAsync(string source, string referenceAssemblyGroup)
{
ReferenceAssemblies referenceAssemblies = ReferenceAssemblyCatalog.Catalog[referenceAssemblyGroup];

await new Test<TAnalyzer, EmptyCodeFixProvider>
{
TestCode = source,
FixedCode = source,
ReferenceAssemblies = referenceAssemblies,
}.RunAsync().ConfigureAwait(false);
}
}
6 changes: 5 additions & 1 deletion Source/Moq.Analyzers.Test/Helpers/CodeFixVerifier.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;

namespace Moq.Analyzers.Test.Helpers;

internal static class CodeFixVerifier<TAnalyzer, TCodeFixProvider>
where TAnalyzer : DiagnosticAnalyzer, new()
where TCodeFixProvider : CodeFixProvider, new()
{
public static async Task VerifyCodeFixAsync(string originalSource, string fixedSource)
public static async Task VerifyCodeFixAsync(string originalSource, string fixedSource, string referenceAssemblyGroup)
{
ReferenceAssemblies referenceAssemblies = ReferenceAssemblyCatalog.Catalog[referenceAssemblyGroup];

await new Test<TAnalyzer, TCodeFixProvider>
{
TestCode = originalSource,
FixedCode = fixedSource,
ReferenceAssemblies = referenceAssemblies,
}.RunAsync().ConfigureAwait(false);
}
}
22 changes: 16 additions & 6 deletions Source/Moq.Analyzers.Test/Helpers/ReferenceAssemblyCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ namespace Moq.Analyzers.Test.Helpers;
/// package resolution only happens once for a given configuration.
/// </summary>
/// <remarks>
/// This class is currently very simple and assumes that the only package that will be resolved is Moq for .NET 8.0. As our testing needs
/// get more complicated, we can either manage the combinations ourselves
/// (as done in https://github.com/dotnet/roslyn-analyzers/blob/4d5fd9da36d64d4c3370b8813122e226844fc6ed/src/Test.Utilities/AdditionalMetadataReferences.cs)
/// or consider filing an issue in https://github.com/dotnet/roslyn-sdk to clarify best practices.
/// It would be more straightforward to pass around ReferenceAssemblies instances directly, but using non-primitive types causes
/// Visual Studio's Test Explorer to collapse all test cases down to a single entry, which makes it harder to see which test cases
/// are failing or debug a single test case.
/// </remarks>
internal static class ReferenceAssemblyCatalog
{
// TODO: We should also be testing a newer version of Moq. See https://github.com/rjmurillo/moq.analyzers/issues/58.
public static ReferenceAssemblies Net80WithOldMoq { get; } = ReferenceAssemblies.Net.Net80.AddPackages([new PackageIdentity("Moq", "4.8.2")]);
public static string Net80WithOldMoq => nameof(Net80WithOldMoq);

public static string Net80WithNewMoq => nameof(Net80WithNewMoq);

public static IReadOnlyDictionary<string, ReferenceAssemblies> Catalog { get; } = new Dictionary<string, ReferenceAssemblies>(StringComparer.Ordinal)
{
// 4.8.2 was one of the first popular versions of Moq. Ensure this version is prior to 4.13.1, as it changed the internal
// implementation of `.As<T>()` (see https://github.com/devlooped/moq/commit/b552aeddd82090ee0f4743a1ab70a16f3e6d2d11).
{ nameof(Net80WithOldMoq), ReferenceAssemblies.Net.Net80.AddPackages([new PackageIdentity("Moq", "4.8.2")]) },

// 4.18.4 is currently the most downloaded version of Moq.
{ nameof(Net80WithNewMoq), ReferenceAssemblies.Net.Net80.AddPackages([new PackageIdentity("Moq", "4.18.4")]) },
};
}
1 change: 0 additions & 1 deletion Source/Moq.Analyzers.Test/Helpers/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ public Test()

TestState.Sources.Add(globalUsings);
FixedState.Sources.Add(globalUsings);
ReferenceAssemblies = ReferenceAssemblyCatalog.Net80WithOldMoq;
}
}
Loading

0 comments on commit 5057438

Please sign in to comment.