Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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